mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-10 08:09:26 +01:00
VideoCommon: Rework scissor handling
This increases accuracy, fixing the white rendering in Major Minor's Majestic March. However, the hardware backends can only have one viewport and scissor rectangle at a time, while sometimes multiple are needed to accurately emulate what is happening. If possible, this will need to be fixed later.
This commit is contained in:
parent
4595b89ad8
commit
076392a0f6
@ -958,7 +958,7 @@ void Renderer::ClearScreen(const MathUtil::Rectangle<int>& rc, bool colorEnable,
|
|||||||
glDepthMask(m_current_depth_state.updateenable);
|
glDepthMask(m_current_depth_state.updateenable);
|
||||||
|
|
||||||
// Scissor rect must be restored.
|
// Scissor rect must be restored.
|
||||||
BPFunctions::SetScissor();
|
BPFunctions::SetScissorAndViewport();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::RenderXFBToScreen(const MathUtil::Rectangle<int>& target_rc,
|
void Renderer::RenderXFBToScreen(const MathUtil::Rectangle<int>& target_rc,
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "Common/Assert.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
|
|
||||||
@ -37,48 +38,172 @@ void SetGenerationMode()
|
|||||||
g_vertex_manager->SetRasterizationStateChanged();
|
g_vertex_manager->SetRasterizationStateChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetScissor()
|
int ScissorRect::GetArea() const
|
||||||
{
|
{
|
||||||
/* NOTE: the minimum value here for the scissor rect is -342.
|
return rect.GetWidth() * rect.GetHeight();
|
||||||
* GX SDK functions internally add an offset of 342 to scissor coords to
|
}
|
||||||
* ensure that the register was always unsigned.
|
|
||||||
*
|
|
||||||
* The code that was here before tried to "undo" this offset, but
|
|
||||||
* since we always take the difference, the +342 added to both
|
|
||||||
* sides cancels out. */
|
|
||||||
|
|
||||||
/* NOTE: With a positive scissor offset, the scissor rect is shifted left and/or up;
|
int ScissorResult::GetViewportArea(const ScissorRect& rect) const
|
||||||
* With a negative scissor offset, the scissor rect is shifted right and/or down.
|
{
|
||||||
*
|
int x0 = std::clamp<int>(rect.rect.left + rect.x_off, viewport_left, viewport_right);
|
||||||
* GX SDK functions internally add an offset of 342 to scissor offset.
|
int x1 = std::clamp<int>(rect.rect.right + rect.x_off, viewport_left, viewport_right);
|
||||||
* The scissor offset is always even, so to save space, the scissor offset register
|
|
||||||
* is scaled down by 2. So, if somebody calls GX_SetScissorBoxOffset(20, 20);
|
|
||||||
* the registers will be set to ((20 + 342) / 2 = 181, 181).
|
|
||||||
*
|
|
||||||
* The scissor offset register is 10bit signed [-512, 511].
|
|
||||||
* e.g. In Super Mario Galaxy 1 and 2, during the "Boss roar effect",
|
|
||||||
* for a scissor offset of (0, -464), the scissor offset register will be set to
|
|
||||||
* (171, (-464 + 342) / 2 = -61).
|
|
||||||
*/
|
|
||||||
s32 xoff = bpmem.scissorOffset.x * 2;
|
|
||||||
s32 yoff = bpmem.scissorOffset.y * 2;
|
|
||||||
|
|
||||||
MathUtil::Rectangle<int> native_rc(bpmem.scissorTL.x - xoff, bpmem.scissorTL.y - yoff,
|
int y0 = std::clamp<int>(rect.rect.top + rect.y_off, viewport_top, viewport_bottom);
|
||||||
bpmem.scissorBR.x - xoff + 1, bpmem.scissorBR.y - yoff + 1);
|
int y1 = std::clamp<int>(rect.rect.bottom + rect.y_off, viewport_top, viewport_bottom);
|
||||||
native_rc.ClampUL(0, 0, EFB_WIDTH, EFB_HEIGHT);
|
|
||||||
|
|
||||||
auto target_rc = g_renderer->ConvertEFBRectangle(native_rc);
|
return (x1 - x0) * (y1 - y0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare so that a sorted collection of rectangles has the best one last, so that if they're drawn
|
||||||
|
// in order, the best one is the one that is drawn last (and thus over the rest).
|
||||||
|
// The exact iteration order on hardware hasn't been tested, but silly things can happen where a
|
||||||
|
// polygon can intersect with itself; this only applies outside of the viewport region (in areas
|
||||||
|
// that would normally be affected by clipping). No game is known to care about this.
|
||||||
|
bool ScissorResult::IsWorse(const ScissorRect& lhs, const ScissorRect& rhs) const
|
||||||
|
{
|
||||||
|
// First, penalize any rect that is not in the viewport
|
||||||
|
int lhs_area = GetViewportArea(lhs);
|
||||||
|
int rhs_area = GetViewportArea(rhs);
|
||||||
|
|
||||||
|
if (lhs_area != rhs_area)
|
||||||
|
return lhs_area < rhs_area;
|
||||||
|
|
||||||
|
// Now compare on total areas, without regard for the viewport
|
||||||
|
return lhs.GetArea() < rhs.GetArea();
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Dynamically sized small array of ScissorRanges (used as an heap-less alternative to std::vector
|
||||||
|
// to reduce allocation overhead)
|
||||||
|
struct RangeList
|
||||||
|
{
|
||||||
|
static constexpr u32 MAX_RANGES = 9;
|
||||||
|
|
||||||
|
u32 m_num_ranges = 0;
|
||||||
|
std::array<ScissorRange, MAX_RANGES> m_ranges{};
|
||||||
|
|
||||||
|
void AddRange(int offset, int start, int end)
|
||||||
|
{
|
||||||
|
DEBUG_ASSERT(m_num_ranges < MAX_RANGES);
|
||||||
|
m_ranges[m_num_ranges] = ScissorRange(offset, start, end);
|
||||||
|
m_num_ranges++;
|
||||||
|
}
|
||||||
|
auto begin() const { return m_ranges.begin(); }
|
||||||
|
auto end() const { return m_ranges.begin() + m_num_ranges; }
|
||||||
|
|
||||||
|
u32 size() { return m_num_ranges; }
|
||||||
|
};
|
||||||
|
|
||||||
|
static RangeList ComputeScissorRanges(int start, int end, int offset, int efb_dim)
|
||||||
|
{
|
||||||
|
RangeList ranges;
|
||||||
|
|
||||||
|
for (int extra_off = -4096; extra_off <= 4096; extra_off += 1024)
|
||||||
|
{
|
||||||
|
int new_off = offset + extra_off;
|
||||||
|
int new_start = std::clamp(start - new_off, 0, efb_dim);
|
||||||
|
int new_end = std::clamp(end - new_off + 1, 0, efb_dim);
|
||||||
|
if (new_start < new_end)
|
||||||
|
{
|
||||||
|
ranges.AddRange(new_off, new_start, new_end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ScissorResult::ScissorResult(const BPMemory& bpmemory, const XFMemory& xfmemory)
|
||||||
|
: ScissorResult(bpmemory,
|
||||||
|
std::minmax(xfmemory.viewport.xOrig - xfmemory.viewport.wd,
|
||||||
|
xfmemory.viewport.xOrig + xfmemory.viewport.wd),
|
||||||
|
std::minmax(xfmemory.viewport.yOrig - xfmemory.viewport.ht,
|
||||||
|
xfmemory.viewport.yOrig + xfmemory.viewport.ht))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
ScissorResult::ScissorResult(const BPMemory& bpmemory, std::pair<float, float> viewport_x,
|
||||||
|
std::pair<float, float> viewport_y)
|
||||||
|
: scissor_tl{.hex = bpmemory.scissorTL.hex}, scissor_br{.hex = bpmemory.scissorBR.hex},
|
||||||
|
scissor_off{.hex = bpmemory.scissorOffset.hex}, viewport_left(viewport_x.first),
|
||||||
|
viewport_right(viewport_x.second), viewport_top(viewport_y.first),
|
||||||
|
viewport_bottom(viewport_y.second)
|
||||||
|
{
|
||||||
|
// Range is [left, right] and [top, bottom] (closed intervals)
|
||||||
|
const int left = scissor_tl.x;
|
||||||
|
const int right = scissor_br.x;
|
||||||
|
const int top = scissor_tl.y;
|
||||||
|
const int bottom = scissor_br.y;
|
||||||
|
// When left > right or top > bottom, nothing renders (even with wrapping from the offsets)
|
||||||
|
if (left > right || top > bottom)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Note that both the offsets and the coordinates have 342 added to them internally by GX
|
||||||
|
// functions (for the offsets, this is before they are divided by 2/right shifted). This code
|
||||||
|
// could undo both sets of offsets, but it doesn't need to since they cancel out when subtracting
|
||||||
|
// (and those offsets actually matter for the left > right and top > bottom checks).
|
||||||
|
const int x_off = scissor_off.x << 1;
|
||||||
|
const int y_off = scissor_off.y << 1;
|
||||||
|
|
||||||
|
RangeList x_ranges = ComputeScissorRanges(left, right, x_off, EFB_WIDTH);
|
||||||
|
RangeList y_ranges = ComputeScissorRanges(top, bottom, y_off, EFB_HEIGHT);
|
||||||
|
|
||||||
|
m_result.reserve(x_ranges.size() * y_ranges.size());
|
||||||
|
|
||||||
|
// Now we need to form actual rectangles from the x and y ranges,
|
||||||
|
// which is a simple Cartesian product of x_ranges_clamped and y_ranges_clamped.
|
||||||
|
// Each rectangle is also a Cartesian product of x_range and y_range, with
|
||||||
|
// the rectangles being half-open (of the form [x0, x1) X [y0, y1)).
|
||||||
|
for (const auto& x_range : x_ranges)
|
||||||
|
{
|
||||||
|
DEBUG_ASSERT(x_range.start < x_range.end);
|
||||||
|
DEBUG_ASSERT(x_range.end <= EFB_WIDTH);
|
||||||
|
for (const auto& y_range : y_ranges)
|
||||||
|
{
|
||||||
|
DEBUG_ASSERT(y_range.start < y_range.end);
|
||||||
|
DEBUG_ASSERT(y_range.end <= EFB_HEIGHT);
|
||||||
|
m_result.emplace_back(x_range, y_range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cmp = [&](const ScissorRect& lhs, const ScissorRect& rhs) { return IsWorse(lhs, rhs); };
|
||||||
|
std::sort(m_result.begin(), m_result.end(), cmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScissorRect ScissorResult::Best() const
|
||||||
|
{
|
||||||
|
// For now, simply choose the best rectangle (see ScissorResult::IsWorse).
|
||||||
|
// This does mean we calculate all rectangles and only choose one, which is not optimal, but this
|
||||||
|
// is called infrequently. Eventually, all backends will support multiple scissor rects.
|
||||||
|
if (!m_result.empty())
|
||||||
|
{
|
||||||
|
return m_result.back();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// But if we have no rectangles, use a bogus one that's out of bounds.
|
||||||
|
// Ideally, all backends will support multiple scissor rects, in which case this won't be
|
||||||
|
// needed.
|
||||||
|
return ScissorRect(ScissorRange{0, 1000, 1001}, ScissorRange{0, 1000, 1001});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScissorResult ComputeScissorRects()
|
||||||
|
{
|
||||||
|
return ScissorResult{bpmem, xfmem};
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetScissorAndViewport()
|
||||||
|
{
|
||||||
|
auto native_rc = ComputeScissorRects().Best();
|
||||||
|
|
||||||
|
auto target_rc = g_renderer->ConvertEFBRectangle(native_rc.rect);
|
||||||
auto converted_rc =
|
auto converted_rc =
|
||||||
g_renderer->ConvertFramebufferRectangle(target_rc, g_renderer->GetCurrentFramebuffer());
|
g_renderer->ConvertFramebufferRectangle(target_rc, g_renderer->GetCurrentFramebuffer());
|
||||||
g_renderer->SetScissorRect(converted_rc);
|
g_renderer->SetScissorRect(converted_rc);
|
||||||
}
|
|
||||||
|
|
||||||
void SetViewport()
|
float raw_x = (xfmem.viewport.xOrig - native_rc.x_off) - xfmem.viewport.wd;
|
||||||
{
|
float raw_y = (xfmem.viewport.yOrig - native_rc.y_off) + xfmem.viewport.ht;
|
||||||
const s32 xoff = bpmem.scissorOffset.x * 2;
|
|
||||||
const s32 yoff = bpmem.scissorOffset.y * 2;
|
|
||||||
float raw_x = xfmem.viewport.xOrig - xfmem.viewport.wd - xoff;
|
|
||||||
float raw_y = xfmem.viewport.yOrig + xfmem.viewport.ht - yoff;
|
|
||||||
float raw_width = 2.0f * xfmem.viewport.wd;
|
float raw_width = 2.0f * xfmem.viewport.wd;
|
||||||
float raw_height = -2.0f * xfmem.viewport.ht;
|
float raw_height = -2.0f * xfmem.viewport.ht;
|
||||||
if (g_ActiveConfig.UseVertexRounding())
|
if (g_ActiveConfig.UseVertexRounding())
|
||||||
|
@ -7,16 +7,131 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Common/MathUtil.h"
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
struct BPCmd;
|
#include "Common/MathUtil.h"
|
||||||
|
#include "VideoCommon/BPMemory.h"
|
||||||
|
struct XFMemory;
|
||||||
|
|
||||||
namespace BPFunctions
|
namespace BPFunctions
|
||||||
{
|
{
|
||||||
|
struct ScissorRange
|
||||||
|
{
|
||||||
|
constexpr ScissorRange() = default;
|
||||||
|
constexpr ScissorRange(int offset, int start, int end) : offset(offset), start(start), end(end) {}
|
||||||
|
int offset = 0;
|
||||||
|
int start = 0;
|
||||||
|
int end = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ScissorRect
|
||||||
|
{
|
||||||
|
constexpr ScissorRect(ScissorRange x_range, ScissorRange y_range)
|
||||||
|
: // Rectangle ctor takes x0, y0, x1, y1.
|
||||||
|
rect(x_range.start, y_range.start, x_range.end, y_range.end), x_off(x_range.offset),
|
||||||
|
y_off(y_range.offset)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MathUtil::Rectangle<int> rect;
|
||||||
|
int x_off;
|
||||||
|
int y_off;
|
||||||
|
|
||||||
|
int GetArea() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Although the GameCube/Wii have only one scissor configuration and only one viewport
|
||||||
|
// configuration, some values can result in multiple parts of the screen being updated.
|
||||||
|
// This can happen if the scissor offset combined with the bottom or right coordinate ends up
|
||||||
|
// exceeding 1024; then, both sides of the screen will be drawn to, while the middle is not.
|
||||||
|
// Major Minor's Majestic March causes this to happen during loading screens and other scrolling
|
||||||
|
// effects, though it draws on top of one of them.
|
||||||
|
// This can also happen if the scissor rectangle is particularly large, but this will usually
|
||||||
|
// involve drawing content outside of the viewport, which Dolphin does not currently handle.
|
||||||
|
//
|
||||||
|
// The hardware backends can currently only use one viewport and scissor rectangle, so we need to
|
||||||
|
// pick the "best" rectangle based on how much of the viewport would be rendered to the screen.
|
||||||
|
// If we choose the wrong one, then content might not actually show up when the game is expecting it
|
||||||
|
// to. This does happen on Major Minor's Majestic March for the final few frames of the horizontal
|
||||||
|
// scrolling animation, but it isn't that important. Note that the assumption that a "best"
|
||||||
|
// rectangle exists is based on games only wanting to draw one rectangle, and accidentally
|
||||||
|
// configuring the scissor offset and size of the scissor rectangle such that multiple show up;
|
||||||
|
// there are no known games where this is not the case.
|
||||||
|
struct ScissorResult
|
||||||
|
{
|
||||||
|
ScissorResult(const BPMemory& bpmem, const XFMemory& xfmem);
|
||||||
|
~ScissorResult() = default;
|
||||||
|
ScissorResult(const ScissorResult& other)
|
||||||
|
: scissor_tl{.hex = other.scissor_tl.hex}, scissor_br{.hex = other.scissor_br.hex},
|
||||||
|
scissor_off{.hex = other.scissor_off.hex}, viewport_left{other.viewport_left},
|
||||||
|
viewport_right{other.viewport_right}, viewport_top{other.viewport_top},
|
||||||
|
viewport_bottom{other.viewport_bottom}, m_result{other.m_result}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
ScissorResult& operator=(const ScissorResult& other)
|
||||||
|
{
|
||||||
|
if (this == &other)
|
||||||
|
return *this;
|
||||||
|
scissor_tl.hex = other.scissor_tl.hex;
|
||||||
|
scissor_br.hex = other.scissor_br.hex;
|
||||||
|
scissor_off.hex = other.scissor_off.hex;
|
||||||
|
viewport_left = other.viewport_left;
|
||||||
|
viewport_right = other.viewport_right;
|
||||||
|
viewport_top = other.viewport_top;
|
||||||
|
viewport_bottom = other.viewport_bottom;
|
||||||
|
m_result = other.m_result;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ScissorResult(ScissorResult&& other)
|
||||||
|
: scissor_tl{.hex = other.scissor_tl.hex}, scissor_br{.hex = other.scissor_br.hex},
|
||||||
|
scissor_off{.hex = other.scissor_off.hex}, viewport_left{other.viewport_left},
|
||||||
|
viewport_right{other.viewport_right}, viewport_top{other.viewport_top},
|
||||||
|
viewport_bottom{other.viewport_bottom}, m_result{std::move(other.m_result)}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
ScissorResult& operator=(ScissorResult&& other)
|
||||||
|
{
|
||||||
|
if (this == &other)
|
||||||
|
return *this;
|
||||||
|
scissor_tl.hex = other.scissor_tl.hex;
|
||||||
|
scissor_br.hex = other.scissor_br.hex;
|
||||||
|
scissor_off.hex = other.scissor_off.hex;
|
||||||
|
viewport_left = other.viewport_left;
|
||||||
|
viewport_right = other.viewport_right;
|
||||||
|
viewport_top = other.viewport_top;
|
||||||
|
viewport_bottom = other.viewport_bottom;
|
||||||
|
m_result = std::move(other.m_result);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input values, for use in statistics
|
||||||
|
ScissorPos scissor_tl;
|
||||||
|
ScissorPos scissor_br;
|
||||||
|
ScissorOffset scissor_off;
|
||||||
|
float viewport_left;
|
||||||
|
float viewport_right;
|
||||||
|
float viewport_top;
|
||||||
|
float viewport_bottom;
|
||||||
|
|
||||||
|
// Actual result
|
||||||
|
std::vector<ScissorRect> m_result;
|
||||||
|
|
||||||
|
ScissorRect Best() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ScissorResult(const BPMemory& bpmem, std::pair<float, float> viewport_x,
|
||||||
|
std::pair<float, float> viewport_y);
|
||||||
|
|
||||||
|
int GetViewportArea(const ScissorRect& rect) const;
|
||||||
|
bool IsWorse(const ScissorRect& lhs, const ScissorRect& rhs) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
ScissorResult ComputeScissorRects();
|
||||||
|
|
||||||
void FlushPipeline();
|
void FlushPipeline();
|
||||||
void SetGenerationMode();
|
void SetGenerationMode();
|
||||||
void SetScissor();
|
void SetScissorAndViewport();
|
||||||
void SetViewport();
|
|
||||||
void SetDepthMode();
|
void SetDepthMode();
|
||||||
void SetBlendMode();
|
void SetBlendMode();
|
||||||
void ClearScreen(const MathUtil::Rectangle<int>& rc);
|
void ClearScreen(const MathUtil::Rectangle<int>& rc);
|
||||||
|
@ -131,8 +131,6 @@ static void BPWritten(const BPCmd& bp, int cycles_into_future)
|
|||||||
case BPMEM_SCISSORTL: // Scissor Rectable Top, Left
|
case BPMEM_SCISSORTL: // Scissor Rectable Top, Left
|
||||||
case BPMEM_SCISSORBR: // Scissor Rectable Bottom, Right
|
case BPMEM_SCISSORBR: // Scissor Rectable Bottom, Right
|
||||||
case BPMEM_SCISSOROFFSET: // Scissor Offset
|
case BPMEM_SCISSOROFFSET: // Scissor Offset
|
||||||
SetScissor();
|
|
||||||
SetViewport();
|
|
||||||
VertexShaderManager::SetViewportChanged();
|
VertexShaderManager::SetViewportChanged();
|
||||||
GeometryShaderManager::SetViewportChanged();
|
GeometryShaderManager::SetViewportChanged();
|
||||||
return;
|
return;
|
||||||
@ -1272,8 +1270,7 @@ void BPReload()
|
|||||||
// let's not risk actually replaying any writes.
|
// let's not risk actually replaying any writes.
|
||||||
// note that PixelShaderManager is already covered since it has its own DoState.
|
// note that PixelShaderManager is already covered since it has its own DoState.
|
||||||
SetGenerationMode();
|
SetGenerationMode();
|
||||||
SetScissor();
|
SetScissorAndViewport();
|
||||||
SetViewport();
|
|
||||||
SetDepthMode();
|
SetDepthMode();
|
||||||
SetBlendMode();
|
SetBlendMode();
|
||||||
OnPixelFormatChange();
|
OnPixelFormatChange();
|
||||||
|
@ -160,8 +160,7 @@ void Renderer::EndUtilityDrawing()
|
|||||||
{
|
{
|
||||||
// Reset framebuffer/scissor/viewport. Pipeline will be reset at next draw.
|
// Reset framebuffer/scissor/viewport. Pipeline will be reset at next draw.
|
||||||
g_framebuffer_manager->BindEFBFramebuffer();
|
g_framebuffer_manager->BindEFBFramebuffer();
|
||||||
BPFunctions::SetScissor();
|
BPFunctions::SetScissorAndViewport();
|
||||||
BPFunctions::SetViewport();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::SetFramebuffer(AbstractFramebuffer* framebuffer)
|
void Renderer::SetFramebuffer(AbstractFramebuffer* framebuffer)
|
||||||
@ -543,8 +542,7 @@ void Renderer::CheckForConfigChanges()
|
|||||||
// Viewport and scissor rect have to be reset since they will be scaled differently.
|
// Viewport and scissor rect have to be reset since they will be scaled differently.
|
||||||
if (changed_bits & CONFIG_CHANGE_BIT_TARGET_SIZE)
|
if (changed_bits & CONFIG_CHANGE_BIT_TARGET_SIZE)
|
||||||
{
|
{
|
||||||
BPFunctions::SetViewport();
|
BPFunctions::SetScissorAndViewport();
|
||||||
BPFunctions::SetScissor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stereo mode change requires recompiling our post processing pipeline and imgui pipelines for
|
// Stereo mode change requires recompiling our post processing pipeline and imgui pipelines for
|
||||||
|
@ -298,7 +298,7 @@ void VertexShaderManager::SetConstants()
|
|||||||
}
|
}
|
||||||
|
|
||||||
dirty = true;
|
dirty = true;
|
||||||
BPFunctions::SetViewport();
|
BPFunctions::SetScissorAndViewport();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bProjectionChanged || g_freelook_camera.GetController()->IsDirty())
|
if (bProjectionChanged || g_freelook_camera.GetController()->IsDirty())
|
||||||
|
Loading…
x
Reference in New Issue
Block a user