mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-02-02 02:52:30 +02:00
Decouple XFB scanout from presentation
This commit is contained in:
parent
f1e7fb505b
commit
e4b205c769
@ -125,6 +125,7 @@ void FramebufferManager::BindEFBRenderTarget(bool bind_depth)
|
|||||||
|
|
||||||
FramebufferManager::FramebufferManager(int target_width, int target_height)
|
FramebufferManager::FramebufferManager(int target_width, int target_height)
|
||||||
{
|
{
|
||||||
|
static constexpr std::array<float, 4> clear_color = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||||
m_target_width = static_cast<unsigned int>(std::max(target_width, 1));
|
m_target_width = static_cast<unsigned int>(std::max(target_width, 1));
|
||||||
m_target_height = static_cast<unsigned int>(std::max(target_height, 1));
|
m_target_height = static_cast<unsigned int>(std::max(target_height, 1));
|
||||||
DXGI_SAMPLE_DESC sample_desc;
|
DXGI_SAMPLE_DESC sample_desc;
|
||||||
@ -154,6 +155,7 @@ FramebufferManager::FramebufferManager(int target_width, int target_height)
|
|||||||
D3D::SetDebugObjectName(m_efb.color_tex->GetTex(), "EFB color texture");
|
D3D::SetDebugObjectName(m_efb.color_tex->GetTex(), "EFB color texture");
|
||||||
D3D::SetDebugObjectName(m_efb.color_tex->GetSRV(), "EFB color texture shader resource view");
|
D3D::SetDebugObjectName(m_efb.color_tex->GetSRV(), "EFB color texture shader resource view");
|
||||||
D3D::SetDebugObjectName(m_efb.color_tex->GetRTV(), "EFB color texture render target view");
|
D3D::SetDebugObjectName(m_efb.color_tex->GetRTV(), "EFB color texture render target view");
|
||||||
|
D3D::context->ClearRenderTargetView(m_efb.color_tex->GetRTV(), clear_color.data());
|
||||||
|
|
||||||
// Temporary EFB color texture - used in ReinterpretPixelData
|
// Temporary EFB color texture - used in ReinterpretPixelData
|
||||||
texdesc =
|
texdesc =
|
||||||
@ -173,6 +175,7 @@ FramebufferManager::FramebufferManager(int target_width, int target_height)
|
|||||||
"EFB color temp texture shader resource view");
|
"EFB color temp texture shader resource view");
|
||||||
D3D::SetDebugObjectName(m_efb.color_temp_tex->GetRTV(),
|
D3D::SetDebugObjectName(m_efb.color_temp_tex->GetRTV(),
|
||||||
"EFB color temp texture render target view");
|
"EFB color temp texture render target view");
|
||||||
|
D3D::context->ClearRenderTargetView(m_efb.color_temp_tex->GetRTV(), clear_color.data());
|
||||||
|
|
||||||
// Integer render targets for EFB, used for logic op
|
// Integer render targets for EFB, used for logic op
|
||||||
CD3D11_RENDER_TARGET_VIEW_DESC int_rtv_desc(m_efb.color_tex->GetTex(),
|
CD3D11_RENDER_TARGET_VIEW_DESC int_rtv_desc(m_efb.color_tex->GetTex(),
|
||||||
@ -222,6 +225,7 @@ FramebufferManager::FramebufferManager(int target_width, int target_height)
|
|||||||
D3D::SetDebugObjectName(m_efb.depth_tex->GetTex(), "EFB depth texture");
|
D3D::SetDebugObjectName(m_efb.depth_tex->GetTex(), "EFB depth texture");
|
||||||
D3D::SetDebugObjectName(m_efb.depth_tex->GetDSV(), "EFB depth texture depth stencil view");
|
D3D::SetDebugObjectName(m_efb.depth_tex->GetDSV(), "EFB depth texture depth stencil view");
|
||||||
D3D::SetDebugObjectName(m_efb.depth_tex->GetSRV(), "EFB depth texture shader resource view");
|
D3D::SetDebugObjectName(m_efb.depth_tex->GetSRV(), "EFB depth texture shader resource view");
|
||||||
|
D3D::context->ClearDepthStencilView(m_efb.depth_tex->GetDSV(), D3D11_CLEAR_DEPTH, 0.0f, 0);
|
||||||
|
|
||||||
// Render buffer for AccessEFB (depth data)
|
// Render buffer for AccessEFB (depth data)
|
||||||
texdesc = CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_R32_FLOAT, 1, 1, 1, 1, D3D11_BIND_RENDER_TARGET);
|
texdesc = CD3D11_TEXTURE2D_DESC(DXGI_FORMAT_R32_FLOAT, 1, 1, 1, 1, D3D11_BIND_RENDER_TARGET);
|
||||||
|
@ -66,19 +66,10 @@ Renderer::Renderer(int backbuffer_width, int backbuffer_height, float backbuffer
|
|||||||
: ::Renderer(backbuffer_width, backbuffer_height, backbuffer_scale,
|
: ::Renderer(backbuffer_width, backbuffer_height, backbuffer_scale,
|
||||||
AbstractTextureFormat::RGBA8)
|
AbstractTextureFormat::RGBA8)
|
||||||
{
|
{
|
||||||
m_last_multisamples = g_ActiveConfig.iMultisamples;
|
|
||||||
m_last_stereo_mode = g_ActiveConfig.stereo_mode != StereoMode::Off;
|
|
||||||
m_last_fullscreen_state = D3D::GetFullscreenState();
|
m_last_fullscreen_state = D3D::GetFullscreenState();
|
||||||
g_framebuffer_manager = std::make_unique<FramebufferManager>(m_target_width, m_target_height);
|
g_framebuffer_manager = std::make_unique<FramebufferManager>(m_target_width, m_target_height);
|
||||||
SetupDeviceObjects();
|
SetupDeviceObjects();
|
||||||
|
|
||||||
// Clear EFB textures
|
|
||||||
constexpr std::array<float, 4> clear_color{{0.f, 0.f, 0.f, 1.f}};
|
|
||||||
D3D::context->ClearRenderTargetView(FramebufferManager::GetEFBColorTexture()->GetRTV(),
|
|
||||||
clear_color.data());
|
|
||||||
D3D::context->ClearDepthStencilView(FramebufferManager::GetEFBDepthTexture()->GetDSV(),
|
|
||||||
D3D11_CLEAR_DEPTH, 0.f, 0);
|
|
||||||
|
|
||||||
D3D11_VIEWPORT vp = CD3D11_VIEWPORT(0.f, 0.f, (float)m_target_width, (float)m_target_height);
|
D3D11_VIEWPORT vp = CD3D11_VIEWPORT(0.f, 0.f, (float)m_target_width, (float)m_target_height);
|
||||||
D3D::context->RSSetViewports(1, &vp);
|
D3D::context->RSSetViewports(1, &vp);
|
||||||
FramebufferManager::BindEFBRenderTarget();
|
FramebufferManager::BindEFBRenderTarget();
|
||||||
@ -560,68 +551,33 @@ void Renderer::ReinterpretPixelData(unsigned int convtype)
|
|||||||
RestoreAPIState();
|
RestoreAPIState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function has the final picture. We adjust the aspect ratio here.
|
void Renderer::BindBackbuffer(const ClearColor& clear_color)
|
||||||
void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region, u64 ticks)
|
|
||||||
{
|
{
|
||||||
ResetAPIState();
|
|
||||||
|
|
||||||
// Prepare to copy the XFBs to our backbuffer
|
|
||||||
CheckForSurfaceChange();
|
CheckForSurfaceChange();
|
||||||
CheckForSurfaceResize();
|
CheckForSurfaceResize();
|
||||||
UpdateDrawRectangle();
|
|
||||||
|
|
||||||
TargetRectangle targetRc = GetTargetRectangle();
|
|
||||||
static constexpr std::array<float, 4> clear_color{{0.f, 0.f, 0.f, 1.f}};
|
|
||||||
D3D::context->OMSetRenderTargets(1, &D3D::GetBackBuffer()->GetRTV(), nullptr);
|
D3D::context->OMSetRenderTargets(1, &D3D::GetBackBuffer()->GetRTV(), nullptr);
|
||||||
D3D::context->ClearRenderTargetView(D3D::GetBackBuffer()->GetRTV(), clear_color.data());
|
D3D::context->ClearRenderTargetView(D3D::GetBackBuffer()->GetRTV(), clear_color.data());
|
||||||
m_current_framebuffer = nullptr;
|
m_current_framebuffer = nullptr;
|
||||||
m_current_framebuffer_width = m_backbuffer_width;
|
m_current_framebuffer_width = m_backbuffer_width;
|
||||||
m_current_framebuffer_height = m_backbuffer_height;
|
m_current_framebuffer_height = m_backbuffer_height;
|
||||||
|
}
|
||||||
|
|
||||||
// activate linear filtering for the buffer copies
|
void Renderer::PresentBackbuffer()
|
||||||
D3D::SetLinearCopySampler();
|
{
|
||||||
auto* xfb_texture = static_cast<DXTexture*>(texture);
|
D3D::Present();
|
||||||
|
}
|
||||||
BlitScreen(xfb_region, targetRc, xfb_texture->GetRawTexIdentifier(),
|
|
||||||
xfb_texture->GetConfig().width, xfb_texture->GetConfig().height);
|
|
||||||
|
|
||||||
// Reset viewport for drawing text
|
|
||||||
D3D11_VIEWPORT vp = CD3D11_VIEWPORT(0.0f, 0.0f, static_cast<float>(m_backbuffer_width),
|
|
||||||
static_cast<float>(m_backbuffer_height));
|
|
||||||
D3D::context->RSSetViewports(1, &vp);
|
|
||||||
DrawImGui();
|
|
||||||
|
|
||||||
g_texture_cache->Cleanup(frameCount);
|
|
||||||
|
|
||||||
// Enable configuration changes
|
|
||||||
UpdateActiveConfig();
|
|
||||||
g_texture_cache->OnConfigChanged(g_ActiveConfig);
|
|
||||||
|
|
||||||
// Flip/present backbuffer to frontbuffer here
|
|
||||||
if (D3D::swapchain)
|
|
||||||
D3D::Present();
|
|
||||||
|
|
||||||
|
void Renderer::OnConfigChanged(u32 bits)
|
||||||
|
{
|
||||||
// Resize the back buffers NOW to avoid flickering
|
// Resize the back buffers NOW to avoid flickering
|
||||||
if (CalculateTargetSize() || m_last_multisamples != g_ActiveConfig.iMultisamples ||
|
if (bits & (CONFIG_CHANGE_BIT_TARGET_SIZE | CONFIG_CHANGE_BIT_MULTISAMPLES |
|
||||||
m_last_stereo_mode != (g_ActiveConfig.stereo_mode != StereoMode::Off))
|
CONFIG_CHANGE_BIT_STEREO_MODE))
|
||||||
{
|
{
|
||||||
m_last_multisamples = g_ActiveConfig.iMultisamples;
|
|
||||||
m_last_stereo_mode = g_ActiveConfig.stereo_mode != StereoMode::Off;
|
|
||||||
PixelShaderCache::InvalidateMSAAShaders();
|
PixelShaderCache::InvalidateMSAAShaders();
|
||||||
UpdateDrawRectangle();
|
|
||||||
|
|
||||||
g_framebuffer_manager.reset();
|
g_framebuffer_manager.reset();
|
||||||
g_framebuffer_manager = std::make_unique<FramebufferManager>(m_target_width, m_target_height);
|
g_framebuffer_manager = std::make_unique<FramebufferManager>(m_target_width, m_target_height);
|
||||||
D3D::context->ClearRenderTargetView(FramebufferManager::GetEFBColorTexture()->GetRTV(),
|
|
||||||
clear_color.data());
|
|
||||||
D3D::context->ClearDepthStencilView(FramebufferManager::GetEFBDepthTexture()->GetDSV(),
|
|
||||||
D3D11_CLEAR_DEPTH, 0.f, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckForHostConfigChanges();
|
|
||||||
|
|
||||||
// begin next frame
|
|
||||||
RestoreAPIState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::CheckForSurfaceChange()
|
void Renderer::CheckForSurfaceChange()
|
||||||
@ -780,28 +736,34 @@ void Renderer::BBoxWrite(int index, u16 _value)
|
|||||||
BBox::Set(index, value);
|
BBox::Set(index, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::BlitScreen(TargetRectangle src, TargetRectangle dst, D3DTexture2D* src_texture,
|
void Renderer::RenderXFBToScreen(const AbstractTexture* texture, const EFBRectangle& rc)
|
||||||
u32 src_width, u32 src_height)
|
|
||||||
{
|
{
|
||||||
|
const CD3D11_RECT source_rc(rc.left, rc.top, rc.right, rc.bottom);
|
||||||
|
const TargetRectangle target_rc = GetTargetRectangle();
|
||||||
|
|
||||||
|
// activate linear filtering for the buffer copies
|
||||||
|
D3D::SetLinearCopySampler();
|
||||||
|
|
||||||
if (g_ActiveConfig.stereo_mode == StereoMode::SBS ||
|
if (g_ActiveConfig.stereo_mode == StereoMode::SBS ||
|
||||||
g_ActiveConfig.stereo_mode == StereoMode::TAB)
|
g_ActiveConfig.stereo_mode == StereoMode::TAB)
|
||||||
{
|
{
|
||||||
TargetRectangle leftRc, rightRc;
|
TargetRectangle left_rc, right_rc;
|
||||||
std::tie(leftRc, rightRc) = ConvertStereoRectangle(dst);
|
std::tie(left_rc, right_rc) = ConvertStereoRectangle(target_rc);
|
||||||
|
|
||||||
D3D11_VIEWPORT leftVp = CD3D11_VIEWPORT((float)leftRc.left, (float)leftRc.top,
|
SetViewport(static_cast<float>(left_rc.left), static_cast<float>(left_rc.top),
|
||||||
(float)leftRc.GetWidth(), (float)leftRc.GetHeight());
|
static_cast<float>(left_rc.GetWidth()), static_cast<float>(right_rc.GetHeight()),
|
||||||
D3D11_VIEWPORT rightVp = CD3D11_VIEWPORT((float)rightRc.left, (float)rightRc.top,
|
0.0f, 1.0f);
|
||||||
(float)rightRc.GetWidth(), (float)rightRc.GetHeight());
|
D3D::drawShadedTexQuad(static_cast<const DXTexture*>(texture)->GetRawTexIdentifier()->GetSRV(),
|
||||||
|
&source_rc, texture->GetWidth(), texture->GetHeight(),
|
||||||
D3D::context->RSSetViewports(1, &leftVp);
|
|
||||||
D3D::drawShadedTexQuad(src_texture->GetSRV(), src.AsRECT(), src_width, src_height,
|
|
||||||
PixelShaderCache::GetColorCopyProgram(false),
|
PixelShaderCache::GetColorCopyProgram(false),
|
||||||
VertexShaderCache::GetSimpleVertexShader(),
|
VertexShaderCache::GetSimpleVertexShader(),
|
||||||
VertexShaderCache::GetSimpleInputLayout(), nullptr, 0);
|
VertexShaderCache::GetSimpleInputLayout(), nullptr, 0);
|
||||||
|
|
||||||
D3D::context->RSSetViewports(1, &rightVp);
|
SetViewport(static_cast<float>(right_rc.left), static_cast<float>(right_rc.top),
|
||||||
D3D::drawShadedTexQuad(src_texture->GetSRV(), src.AsRECT(), src_width, src_height,
|
static_cast<float>(right_rc.GetWidth()), static_cast<float>(right_rc.GetHeight()),
|
||||||
|
0.0f, 1.0f);
|
||||||
|
D3D::drawShadedTexQuad(static_cast<const DXTexture*>(texture)->GetRawTexIdentifier()->GetSRV(),
|
||||||
|
&source_rc, texture->GetWidth(), texture->GetHeight(),
|
||||||
PixelShaderCache::GetColorCopyProgram(false),
|
PixelShaderCache::GetColorCopyProgram(false),
|
||||||
VertexShaderCache::GetSimpleVertexShader(),
|
VertexShaderCache::GetSimpleVertexShader(),
|
||||||
VertexShaderCache::GetSimpleInputLayout(), nullptr, 1);
|
VertexShaderCache::GetSimpleInputLayout(), nullptr, 1);
|
||||||
@ -811,29 +773,33 @@ void Renderer::BlitScreen(TargetRectangle src, TargetRectangle dst, D3DTexture2D
|
|||||||
if (!m_3d_vision_texture)
|
if (!m_3d_vision_texture)
|
||||||
Create3DVisionTexture(m_backbuffer_width, m_backbuffer_height);
|
Create3DVisionTexture(m_backbuffer_width, m_backbuffer_height);
|
||||||
|
|
||||||
D3D11_VIEWPORT leftVp = CD3D11_VIEWPORT((float)dst.left, (float)dst.top, (float)dst.GetWidth(),
|
const CD3D11_VIEWPORT left_vp(
|
||||||
(float)dst.GetHeight());
|
static_cast<float>(target_rc.left), static_cast<float>(target_rc.top),
|
||||||
D3D11_VIEWPORT rightVp = CD3D11_VIEWPORT((float)(dst.left + m_backbuffer_width), (float)dst.top,
|
static_cast<float>(target_rc.GetWidth()), static_cast<float>(target_rc.GetHeight()));
|
||||||
(float)dst.GetWidth(), (float)dst.GetHeight());
|
const CD3D11_VIEWPORT right_vp(
|
||||||
|
static_cast<float>(target_rc.left + m_backbuffer_width), static_cast<float>(target_rc.top),
|
||||||
|
static_cast<float>(target_rc.GetWidth()), static_cast<float>(target_rc.GetHeight()));
|
||||||
|
|
||||||
// Render to staging texture which is double the width of the backbuffer
|
// Render to staging texture which is double the width of the backbuffer
|
||||||
D3D::context->OMSetRenderTargets(1, &m_3d_vision_texture->GetRTV(), nullptr);
|
D3D::context->OMSetRenderTargets(1, &m_3d_vision_texture->GetRTV(), nullptr);
|
||||||
|
|
||||||
D3D::context->RSSetViewports(1, &leftVp);
|
D3D::context->RSSetViewports(1, &left_vp);
|
||||||
D3D::drawShadedTexQuad(src_texture->GetSRV(), src.AsRECT(), src_width, src_height,
|
D3D::drawShadedTexQuad(static_cast<const DXTexture*>(texture)->GetRawTexIdentifier()->GetSRV(),
|
||||||
|
&source_rc, texture->GetWidth(), texture->GetHeight(),
|
||||||
PixelShaderCache::GetColorCopyProgram(false),
|
PixelShaderCache::GetColorCopyProgram(false),
|
||||||
VertexShaderCache::GetSimpleVertexShader(),
|
VertexShaderCache::GetSimpleVertexShader(),
|
||||||
VertexShaderCache::GetSimpleInputLayout(), nullptr, 0);
|
VertexShaderCache::GetSimpleInputLayout(), nullptr, 0);
|
||||||
|
|
||||||
D3D::context->RSSetViewports(1, &rightVp);
|
D3D::context->RSSetViewports(1, &right_vp);
|
||||||
D3D::drawShadedTexQuad(src_texture->GetSRV(), src.AsRECT(), src_width, src_height,
|
D3D::drawShadedTexQuad(static_cast<const DXTexture*>(texture)->GetRawTexIdentifier()->GetSRV(),
|
||||||
|
&source_rc, texture->GetWidth(), texture->GetHeight(),
|
||||||
PixelShaderCache::GetColorCopyProgram(false),
|
PixelShaderCache::GetColorCopyProgram(false),
|
||||||
VertexShaderCache::GetSimpleVertexShader(),
|
VertexShaderCache::GetSimpleVertexShader(),
|
||||||
VertexShaderCache::GetSimpleInputLayout(), nullptr, 1);
|
VertexShaderCache::GetSimpleInputLayout(), nullptr, 1);
|
||||||
|
|
||||||
// Copy the left eye to the backbuffer, if Nvidia 3D Vision is enabled it should
|
// Copy the left eye to the backbuffer, if Nvidia 3D Vision is enabled it should
|
||||||
// recognize the signature and automatically include the right eye frame.
|
// recognize the signature and automatically include the right eye frame.
|
||||||
D3D11_BOX box = CD3D11_BOX(0, 0, 0, m_backbuffer_width, m_backbuffer_height, 1);
|
const CD3D11_BOX box(0, 0, 0, m_backbuffer_width, m_backbuffer_height, 1);
|
||||||
D3D::context->CopySubresourceRegion(D3D::GetBackBuffer()->GetTex(), 0, 0, 0, 0,
|
D3D::context->CopySubresourceRegion(D3D::GetBackBuffer()->GetTex(), 0, 0, 0, 0,
|
||||||
m_3d_vision_texture->GetTex(), 0, &box);
|
m_3d_vision_texture->GetTex(), 0, &box);
|
||||||
|
|
||||||
@ -842,9 +808,9 @@ void Renderer::BlitScreen(TargetRectangle src, TargetRectangle dst, D3DTexture2D
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
D3D11_VIEWPORT vp = CD3D11_VIEWPORT((float)dst.left, (float)dst.top, (float)dst.GetWidth(),
|
SetViewport(static_cast<float>(target_rc.left), static_cast<float>(target_rc.top),
|
||||||
(float)dst.GetHeight());
|
static_cast<float>(target_rc.GetWidth()), static_cast<float>(target_rc.GetHeight()),
|
||||||
D3D::context->RSSetViewports(1, &vp);
|
0.0f, 1.0f);
|
||||||
|
|
||||||
ID3D11PixelShader* pixelShader = (g_Config.stereo_mode == StereoMode::Anaglyph) ?
|
ID3D11PixelShader* pixelShader = (g_Config.stereo_mode == StereoMode::Anaglyph) ?
|
||||||
PixelShaderCache::GetAnaglyphProgram() :
|
PixelShaderCache::GetAnaglyphProgram() :
|
||||||
@ -852,7 +818,8 @@ void Renderer::BlitScreen(TargetRectangle src, TargetRectangle dst, D3DTexture2D
|
|||||||
ID3D11GeometryShader* geomShader = (g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer) ?
|
ID3D11GeometryShader* geomShader = (g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer) ?
|
||||||
GeometryShaderCache::GetCopyGeometryShader() :
|
GeometryShaderCache::GetCopyGeometryShader() :
|
||||||
nullptr;
|
nullptr;
|
||||||
D3D::drawShadedTexQuad(src_texture->GetSRV(), src.AsRECT(), src_width, src_height, pixelShader,
|
D3D::drawShadedTexQuad(static_cast<const DXTexture*>(texture)->GetRawTexIdentifier()->GetSRV(),
|
||||||
|
&source_rc, texture->GetWidth(), texture->GetHeight(), pixelShader,
|
||||||
VertexShaderCache::GetSimpleVertexShader(),
|
VertexShaderCache::GetSimpleVertexShader(),
|
||||||
VertexShaderCache::GetSimpleInputLayout(), geomShader);
|
VertexShaderCache::GetSimpleInputLayout(), geomShader);
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,8 @@ public:
|
|||||||
float far_depth) override;
|
float far_depth) override;
|
||||||
void Draw(u32 base_vertex, u32 num_vertices) override;
|
void Draw(u32 base_vertex, u32 num_vertices) override;
|
||||||
void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) override;
|
void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) override;
|
||||||
|
void BindBackbuffer(const ClearColor& clear_color = {}) override;
|
||||||
|
void PresentBackbuffer() override;
|
||||||
void SetFullscreen(bool enable_fullscreen) override;
|
void SetFullscreen(bool enable_fullscreen) override;
|
||||||
bool IsFullscreen() const override;
|
bool IsFullscreen() const override;
|
||||||
|
|
||||||
@ -66,7 +68,8 @@ public:
|
|||||||
|
|
||||||
TargetRectangle ConvertEFBRectangle(const EFBRectangle& rc) override;
|
TargetRectangle ConvertEFBRectangle(const EFBRectangle& rc) override;
|
||||||
|
|
||||||
void SwapImpl(AbstractTexture* texture, const EFBRectangle& rc, u64 ticks) override;
|
void RenderXFBToScreen(const AbstractTexture* texture, const EFBRectangle& rc) override;
|
||||||
|
void OnConfigChanged(u32 bits) override;
|
||||||
|
|
||||||
void ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable,
|
void ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable,
|
||||||
u32 color, u32 z) override;
|
u32 color, u32 z) override;
|
||||||
@ -81,9 +84,6 @@ private:
|
|||||||
void CheckForSurfaceResize();
|
void CheckForSurfaceResize();
|
||||||
void UpdateBackbufferSize();
|
void UpdateBackbufferSize();
|
||||||
|
|
||||||
void BlitScreen(TargetRectangle src, TargetRectangle dst, D3DTexture2D* src_texture,
|
|
||||||
u32 src_width, u32 src_height);
|
|
||||||
|
|
||||||
StateCache m_state_cache;
|
StateCache m_state_cache;
|
||||||
|
|
||||||
std::array<ID3D11BlendState*, 4> m_clear_blend_states{};
|
std::array<ID3D11BlendState*, 4> m_clear_blend_states{};
|
||||||
@ -95,8 +95,6 @@ private:
|
|||||||
ID3D11Texture2D* m_screenshot_texture = nullptr;
|
ID3D11Texture2D* m_screenshot_texture = nullptr;
|
||||||
D3DTexture2D* m_3d_vision_texture = nullptr;
|
D3DTexture2D* m_3d_vision_texture = nullptr;
|
||||||
|
|
||||||
u32 m_last_multisamples = 1;
|
|
||||||
bool m_last_stereo_mode = false;
|
|
||||||
bool m_last_fullscreen_state = false;
|
bool m_last_fullscreen_state = false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -157,13 +157,14 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi)
|
|||||||
VertexShaderCache::Init();
|
VertexShaderCache::Init();
|
||||||
PixelShaderCache::Init();
|
PixelShaderCache::Init();
|
||||||
GeometryShaderCache::Init();
|
GeometryShaderCache::Init();
|
||||||
if (!g_shader_cache->Initialize())
|
|
||||||
|
if (!g_renderer->Initialize() || !g_shader_cache->Initialize())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
D3D::InitUtils();
|
D3D::InitUtils();
|
||||||
BBox::Init();
|
BBox::Init();
|
||||||
|
|
||||||
return g_renderer->Initialize();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoBackend::Shutdown()
|
void VideoBackend::Shutdown()
|
||||||
|
@ -92,9 +92,4 @@ TargetRectangle Renderer::ConvertEFBRectangle(const EFBRectangle& rc)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::SwapImpl(AbstractTexture*, const EFBRectangle&, u64)
|
|
||||||
{
|
|
||||||
UpdateActiveConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Null
|
} // namespace Null
|
||||||
|
@ -35,8 +35,6 @@ public:
|
|||||||
void BBoxWrite(int index, u16 value) override {}
|
void BBoxWrite(int index, u16 value) override {}
|
||||||
TargetRectangle ConvertEFBRectangle(const EFBRectangle& rc) override;
|
TargetRectangle ConvertEFBRectangle(const EFBRectangle& rc) override;
|
||||||
|
|
||||||
void SwapImpl(AbstractTexture* texture, const EFBRectangle& rc, u64 ticks) override;
|
|
||||||
|
|
||||||
void ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable,
|
void ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable,
|
||||||
u32 color, u32 z) override
|
u32 color, u32 z) override
|
||||||
{
|
{
|
||||||
|
@ -59,10 +59,6 @@ VideoConfig g_ogl_config;
|
|||||||
|
|
||||||
// 1 for no MSAA. Use s_MSAASamples > 1 to check for MSAA.
|
// 1 for no MSAA. Use s_MSAASamples > 1 to check for MSAA.
|
||||||
static int s_MSAASamples = 1;
|
static int s_MSAASamples = 1;
|
||||||
static u32 s_last_multisamples = 1;
|
|
||||||
static bool s_last_stereo_mode = false;
|
|
||||||
|
|
||||||
static bool s_vsync;
|
|
||||||
|
|
||||||
// EFB cache related
|
// EFB cache related
|
||||||
static const u32 EFB_CACHE_RECT_SIZE = 64; // Cache 64x64 blocks.
|
static const u32 EFB_CACHE_RECT_SIZE = 64; // Cache 64x64 blocks.
|
||||||
@ -725,9 +721,6 @@ Renderer::Renderer(std::unique_ptr<GLContext> main_gl_context, float backbuffer_
|
|||||||
g_Config.VerifyValidity();
|
g_Config.VerifyValidity();
|
||||||
UpdateActiveConfig();
|
UpdateActiveConfig();
|
||||||
|
|
||||||
// Since we modify the config here, we need to update the last host bits, it may have changed.
|
|
||||||
m_last_host_config_bits = ShaderHostConfig::GetCurrent().bits;
|
|
||||||
|
|
||||||
OSD::AddMessage(StringFromFormat("Video Info: %s, %s, %s", g_ogl_config.gl_vendor,
|
OSD::AddMessage(StringFromFormat("Video Info: %s, %s, %s", g_ogl_config.gl_vendor,
|
||||||
g_ogl_config.gl_renderer, g_ogl_config.gl_version),
|
g_ogl_config.gl_renderer, g_ogl_config.gl_version),
|
||||||
5000);
|
5000);
|
||||||
@ -756,15 +749,9 @@ Renderer::Renderer(std::unique_ptr<GLContext> main_gl_context, float backbuffer_
|
|||||||
g_ogl_config.bSupportsCopySubImage ? "" : "CopyImageSubData ",
|
g_ogl_config.bSupportsCopySubImage ? "" : "CopyImageSubData ",
|
||||||
g_ActiveConfig.backend_info.bSupportsDepthClamp ? "" : "DepthClamp ");
|
g_ActiveConfig.backend_info.bSupportsDepthClamp ? "" : "DepthClamp ");
|
||||||
|
|
||||||
s_last_multisamples = g_ActiveConfig.iMultisamples;
|
|
||||||
s_MSAASamples = s_last_multisamples;
|
|
||||||
|
|
||||||
s_last_stereo_mode = g_ActiveConfig.stereo_mode != StereoMode::Off;
|
|
||||||
|
|
||||||
// Handle VSync on/off
|
// Handle VSync on/off
|
||||||
s_vsync = g_ActiveConfig.IsVSync();
|
|
||||||
if (!DriverDetails::HasBug(DriverDetails::BUG_BROKEN_VSYNC))
|
if (!DriverDetails::HasBug(DriverDetails::BUG_BROKEN_VSYNC))
|
||||||
m_main_gl_context->SwapInterval(s_vsync);
|
m_main_gl_context->SwapInterval(g_ActiveConfig.IsVSync());
|
||||||
|
|
||||||
// Because of the fixed framebuffer size we need to disable the resolution
|
// Because of the fixed framebuffer size we need to disable the resolution
|
||||||
// options while running
|
// options while running
|
||||||
@ -1220,37 +1207,55 @@ void Renderer::ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaE
|
|||||||
ClearEFBCache();
|
ClearEFBCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::BlitScreen(TargetRectangle src, TargetRectangle dst, GLuint src_texture,
|
void Renderer::RenderXFBToScreen(const AbstractTexture* texture, const EFBRectangle& rc)
|
||||||
int src_width, int src_height)
|
|
||||||
{
|
{
|
||||||
|
TargetRectangle source_rc = rc;
|
||||||
|
source_rc.top = rc.GetHeight();
|
||||||
|
source_rc.bottom = 0;
|
||||||
|
|
||||||
|
// Check if we need to render to a new surface.
|
||||||
|
TargetRectangle flipped_trc = GetTargetRectangle();
|
||||||
|
std::swap(flipped_trc.top, flipped_trc.bottom);
|
||||||
|
|
||||||
|
// Copy the framebuffer to screen.
|
||||||
OpenGLPostProcessing* post_processor = static_cast<OpenGLPostProcessing*>(m_post_processor.get());
|
OpenGLPostProcessing* post_processor = static_cast<OpenGLPostProcessing*>(m_post_processor.get());
|
||||||
if (g_ActiveConfig.stereo_mode == StereoMode::SBS ||
|
if (g_ActiveConfig.stereo_mode == StereoMode::SBS ||
|
||||||
g_ActiveConfig.stereo_mode == StereoMode::TAB)
|
g_ActiveConfig.stereo_mode == StereoMode::TAB)
|
||||||
{
|
{
|
||||||
TargetRectangle leftRc, rightRc;
|
TargetRectangle left_rc, right_rc;
|
||||||
|
|
||||||
// Top-and-Bottom mode needs to compensate for inverted vertical screen coordinates.
|
// Top-and-Bottom mode needs to compensate for inverted vertical screen coordinates.
|
||||||
if (g_ActiveConfig.stereo_mode == StereoMode::TAB)
|
if (g_ActiveConfig.stereo_mode == StereoMode::TAB)
|
||||||
std::tie(rightRc, leftRc) = ConvertStereoRectangle(dst);
|
std::tie(right_rc, left_rc) = ConvertStereoRectangle(flipped_trc);
|
||||||
else
|
else
|
||||||
std::tie(leftRc, rightRc) = ConvertStereoRectangle(dst);
|
std::tie(left_rc, right_rc) = ConvertStereoRectangle(flipped_trc);
|
||||||
|
|
||||||
post_processor->BlitFromTexture(src, leftRc, src_texture, src_width, src_height, 0);
|
post_processor->BlitFromTexture(source_rc, left_rc,
|
||||||
post_processor->BlitFromTexture(src, rightRc, src_texture, src_width, src_height, 1);
|
static_cast<const OGLTexture*>(texture)->GetRawTexIdentifier(),
|
||||||
|
texture->GetWidth(), texture->GetHeight(), 0);
|
||||||
|
post_processor->BlitFromTexture(source_rc, right_rc,
|
||||||
|
static_cast<const OGLTexture*>(texture)->GetRawTexIdentifier(),
|
||||||
|
texture->GetWidth(), texture->GetHeight(), 1);
|
||||||
}
|
}
|
||||||
else if (g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer)
|
else if (g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer)
|
||||||
{
|
{
|
||||||
glDrawBuffer(GL_BACK_LEFT);
|
glDrawBuffer(GL_BACK_LEFT);
|
||||||
post_processor->BlitFromTexture(src, dst, src_texture, src_width, src_height, 0);
|
post_processor->BlitFromTexture(source_rc, flipped_trc,
|
||||||
|
static_cast<const OGLTexture*>(texture)->GetRawTexIdentifier(),
|
||||||
|
texture->GetWidth(), texture->GetHeight(), 0);
|
||||||
|
|
||||||
glDrawBuffer(GL_BACK_RIGHT);
|
glDrawBuffer(GL_BACK_RIGHT);
|
||||||
post_processor->BlitFromTexture(src, dst, src_texture, src_width, src_height, 1);
|
post_processor->BlitFromTexture(source_rc, flipped_trc,
|
||||||
|
static_cast<const OGLTexture*>(texture)->GetRawTexIdentifier(),
|
||||||
|
texture->GetWidth(), texture->GetHeight(), 1);
|
||||||
|
|
||||||
glDrawBuffer(GL_BACK);
|
glDrawBuffer(GL_BACK);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
post_processor->BlitFromTexture(src, dst, src_texture, src_width, src_height, 0);
|
post_processor->BlitFromTexture(source_rc, flipped_trc,
|
||||||
|
static_cast<const OGLTexture*>(texture)->GetRawTexIdentifier(),
|
||||||
|
texture->GetWidth(), texture->GetHeight(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1385,8 +1390,20 @@ void Renderer::ApplyBlendingState(const BlendingState state, bool force)
|
|||||||
m_current_blend_state = state;
|
m_current_blend_state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function has the final picture. We adjust the aspect ratio here.
|
void Renderer::BindBackbuffer(const ClearColor& clear_color)
|
||||||
void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region, u64 ticks)
|
{
|
||||||
|
CheckForSurfaceChange();
|
||||||
|
CheckForSurfaceResize();
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
glClearColor(0, 0, 0, 0);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
m_current_framebuffer = nullptr;
|
||||||
|
m_current_framebuffer_width = m_backbuffer_width;
|
||||||
|
m_current_framebuffer_height = m_backbuffer_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::PresentBackbuffer()
|
||||||
{
|
{
|
||||||
if (g_ogl_config.bSupportsDebug)
|
if (g_ogl_config.bSupportsDebug)
|
||||||
{
|
{
|
||||||
@ -1396,72 +1413,22 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region
|
|||||||
glDisable(GL_DEBUG_OUTPUT);
|
glDisable(GL_DEBUG_OUTPUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* xfb_texture = static_cast<OGLTexture*>(texture);
|
// Swap the back and front buffers, presenting the image.
|
||||||
|
m_main_gl_context->Swap();
|
||||||
|
}
|
||||||
|
|
||||||
TargetRectangle sourceRc = xfb_region;
|
void Renderer::OnConfigChanged(u32 bits)
|
||||||
sourceRc.top = xfb_region.GetHeight();
|
{
|
||||||
sourceRc.bottom = 0;
|
if (bits & (CONFIG_CHANGE_BIT_TARGET_SIZE | CONFIG_CHANGE_BIT_MULTISAMPLES |
|
||||||
|
CONFIG_CHANGE_BIT_STEREO_MODE | CONFIG_CHANGE_BIT_BBOX))
|
||||||
ResetAPIState();
|
|
||||||
|
|
||||||
// Check if we need to render to a new surface.
|
|
||||||
CheckForSurfaceChange();
|
|
||||||
CheckForSurfaceResize();
|
|
||||||
UpdateDrawRectangle();
|
|
||||||
TargetRectangle flipped_trc = GetTargetRectangle();
|
|
||||||
std::swap(flipped_trc.top, flipped_trc.bottom);
|
|
||||||
|
|
||||||
// Skip screen rendering when running in headless mode.
|
|
||||||
if (!IsHeadless())
|
|
||||||
{
|
{
|
||||||
// Clear the framebuffer before drawing anything.
|
s_MSAASamples = g_ActiveConfig.iMultisamples;
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
|
||||||
glClearColor(0, 0, 0, 0);
|
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
|
||||||
m_current_framebuffer = nullptr;
|
|
||||||
m_current_framebuffer_width = m_backbuffer_width;
|
|
||||||
m_current_framebuffer_height = m_backbuffer_height;
|
|
||||||
|
|
||||||
// Copy the framebuffer to screen.
|
|
||||||
BlitScreen(sourceRc, flipped_trc, xfb_texture->GetRawTexIdentifier(),
|
|
||||||
xfb_texture->GetConfig().width, xfb_texture->GetConfig().height);
|
|
||||||
|
|
||||||
// Render OSD messages.
|
|
||||||
glViewport(0, 0, m_backbuffer_width, m_backbuffer_height);
|
|
||||||
DrawImGui();
|
|
||||||
|
|
||||||
// Swap the back and front buffers, presenting the image.
|
|
||||||
m_main_gl_context->Swap();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Since we're not swapping in headless mode, ensure all commands are sent to the GPU.
|
|
||||||
// Otherwise the driver could batch several frames togehter.
|
|
||||||
glFlush();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Was the size changed since the last frame?
|
|
||||||
bool target_size_changed = CalculateTargetSize();
|
|
||||||
bool stencil_buffer_enabled =
|
|
||||||
static_cast<FramebufferManager*>(g_framebuffer_manager.get())->HasStencilBuffer();
|
|
||||||
|
|
||||||
bool fb_needs_update = target_size_changed ||
|
|
||||||
s_last_multisamples != g_ActiveConfig.iMultisamples ||
|
|
||||||
stencil_buffer_enabled != BoundingBox::NeedsStencilBuffer() ||
|
|
||||||
s_last_stereo_mode != (g_ActiveConfig.stereo_mode != StereoMode::Off);
|
|
||||||
|
|
||||||
if (fb_needs_update)
|
|
||||||
{
|
|
||||||
s_last_stereo_mode = g_ActiveConfig.stereo_mode != StereoMode::Off;
|
|
||||||
s_last_multisamples = g_ActiveConfig.iMultisamples;
|
|
||||||
s_MSAASamples = s_last_multisamples;
|
|
||||||
|
|
||||||
if (s_MSAASamples > 1 && s_MSAASamples > g_ogl_config.max_samples)
|
if (s_MSAASamples > 1 && s_MSAASamples > g_ogl_config.max_samples)
|
||||||
{
|
{
|
||||||
s_MSAASamples = g_ogl_config.max_samples;
|
s_MSAASamples = g_ogl_config.max_samples;
|
||||||
OSD::AddMessage(
|
OSD::AddMessage(
|
||||||
StringFromFormat("%d Anti Aliasing samples selected, but only %d supported by your GPU.",
|
StringFromFormat("%d Anti Aliasing samples selected, but only %d supported by your GPU.",
|
||||||
s_last_multisamples, g_ogl_config.max_samples),
|
s_MSAASamples, g_ogl_config.max_samples),
|
||||||
10000);
|
10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1469,40 +1436,13 @@ void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region
|
|||||||
g_framebuffer_manager = std::make_unique<FramebufferManager>(
|
g_framebuffer_manager = std::make_unique<FramebufferManager>(
|
||||||
m_target_width, m_target_height, s_MSAASamples, BoundingBox::NeedsStencilBuffer());
|
m_target_width, m_target_height, s_MSAASamples, BoundingBox::NeedsStencilBuffer());
|
||||||
BoundingBox::SetTargetSizeChanged(m_target_width, m_target_height);
|
BoundingBox::SetTargetSizeChanged(m_target_width, m_target_height);
|
||||||
UpdateDrawRectangle();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s_vsync != g_ActiveConfig.IsVSync())
|
if (bits & CONFIG_CHANGE_BIT_VSYNC && !DriverDetails::HasBug(DriverDetails::BUG_BROKEN_VSYNC))
|
||||||
{
|
m_main_gl_context->SwapInterval(g_ActiveConfig.IsVSync());
|
||||||
s_vsync = g_ActiveConfig.IsVSync();
|
|
||||||
if (!DriverDetails::HasBug(DriverDetails::BUG_BROKEN_VSYNC))
|
|
||||||
m_main_gl_context->SwapInterval(s_vsync);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean out old stuff from caches. It's not worth it to clean out the shader caches.
|
if (bits & CONFIG_CHANGE_BIT_ANISOTROPY)
|
||||||
g_texture_cache->Cleanup(frameCount);
|
|
||||||
|
|
||||||
RestoreAPIState();
|
|
||||||
|
|
||||||
g_Config.iSaveTargetId = 0;
|
|
||||||
|
|
||||||
int old_anisotropy = g_ActiveConfig.iMaxAnisotropy;
|
|
||||||
UpdateActiveConfig();
|
|
||||||
g_texture_cache->OnConfigChanged(g_ActiveConfig);
|
|
||||||
|
|
||||||
if (old_anisotropy != g_ActiveConfig.iMaxAnisotropy)
|
|
||||||
g_sampler_cache->Clear();
|
g_sampler_cache->Clear();
|
||||||
|
|
||||||
// Invalidate shader cache when the host config changes.
|
|
||||||
CheckForHostConfigChanges();
|
|
||||||
|
|
||||||
// For testing zbuffer targets.
|
|
||||||
// Renderer::SetZBufferRender();
|
|
||||||
// SaveTexture("tex.png", GL_TEXTURE_2D, s_FakeZTarget,
|
|
||||||
// GetTargetWidth(), GetTargetHeight());
|
|
||||||
|
|
||||||
// Invalidate EFB cache
|
|
||||||
ClearEFBCache();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::Flush()
|
void Renderer::Flush()
|
||||||
@ -1535,15 +1475,6 @@ void Renderer::CheckForSurfaceResize()
|
|||||||
m_backbuffer_height = m_main_gl_context->GetBackBufferHeight();
|
m_backbuffer_height = m_main_gl_context->GetBackBufferHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::DrawEFB(GLuint framebuffer, const TargetRectangle& target_rc,
|
|
||||||
const TargetRectangle& source_rc)
|
|
||||||
{
|
|
||||||
// for msaa mode, we must resolve the efb content to non-msaa
|
|
||||||
GLuint tex = FramebufferManager::ResolveAndGetRenderTarget(source_rc);
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
|
||||||
BlitScreen(source_rc, target_rc, tex, m_target_width, m_target_height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ALWAYS call RestoreAPIState for each ResetAPIState call you're doing
|
// ALWAYS call RestoreAPIState for each ResetAPIState call you're doing
|
||||||
void Renderer::ResetAPIState()
|
void Renderer::ResetAPIState()
|
||||||
{
|
{
|
||||||
@ -1671,6 +1602,7 @@ void Renderer::UnbindTexture(const AbstractTexture* texture)
|
|||||||
|
|
||||||
glActiveTexture(static_cast<GLenum>(GL_TEXTURE0 + i));
|
glActiveTexture(static_cast<GLenum>(GL_TEXTURE0 + i));
|
||||||
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
|
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
|
||||||
|
m_bound_textures[i] = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,6 +118,8 @@ public:
|
|||||||
float far_depth) override;
|
float far_depth) override;
|
||||||
void Draw(u32 base_vertex, u32 num_vertices) override;
|
void Draw(u32 base_vertex, u32 num_vertices) override;
|
||||||
void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) override;
|
void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) override;
|
||||||
|
void BindBackbuffer(const ClearColor& clear_color = {}) override;
|
||||||
|
void PresentBackbuffer() override;
|
||||||
|
|
||||||
u32 AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data) override;
|
u32 AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data) override;
|
||||||
void PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num_points) override;
|
void PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num_points) override;
|
||||||
@ -130,8 +132,9 @@ public:
|
|||||||
|
|
||||||
TargetRectangle ConvertEFBRectangle(const EFBRectangle& rc) override;
|
TargetRectangle ConvertEFBRectangle(const EFBRectangle& rc) override;
|
||||||
|
|
||||||
void SwapImpl(AbstractTexture* texture, const EFBRectangle& rc, u64 ticks) override;
|
|
||||||
void Flush() override;
|
void Flush() override;
|
||||||
|
void RenderXFBToScreen(const AbstractTexture* texture, const EFBRectangle& rc) override;
|
||||||
|
void OnConfigChanged(u32 bits) override;
|
||||||
|
|
||||||
void ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable,
|
void ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable,
|
||||||
u32 color, u32 z) override;
|
u32 color, u32 z) override;
|
||||||
@ -150,12 +153,6 @@ private:
|
|||||||
void UpdateEFBCache(EFBAccessType type, u32 cacheRectIdx, const EFBRectangle& efbPixelRc,
|
void UpdateEFBCache(EFBAccessType type, u32 cacheRectIdx, const EFBRectangle& efbPixelRc,
|
||||||
const TargetRectangle& targetPixelRc, const void* data);
|
const TargetRectangle& targetPixelRc, const void* data);
|
||||||
|
|
||||||
void DrawEFB(GLuint framebuffer, const TargetRectangle& target_rc,
|
|
||||||
const TargetRectangle& source_rc);
|
|
||||||
|
|
||||||
void BlitScreen(TargetRectangle src, TargetRectangle dst, GLuint src_texture, int src_width,
|
|
||||||
int src_height);
|
|
||||||
|
|
||||||
void CheckForSurfaceChange();
|
void CheckForSurfaceChange();
|
||||||
void CheckForSurfaceResize();
|
void CheckForSurfaceResize();
|
||||||
|
|
||||||
|
@ -84,9 +84,9 @@ bool SWOGLWindow::Initialize(const WindowSystemInfo& wsi)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SWOGLWindow::ShowImage(AbstractTexture* image, const EFBRectangle& xfb_region)
|
void SWOGLWindow::ShowImage(const AbstractTexture* image, const EFBRectangle& xfb_region)
|
||||||
{
|
{
|
||||||
SW::SWTexture* sw_image = static_cast<SW::SWTexture*>(image);
|
const SW::SWTexture* sw_image = static_cast<const SW::SWTexture*>(image);
|
||||||
m_gl_context->Update(); // just updates the render window position and the backbuffer size
|
m_gl_context->Update(); // just updates the render window position and the backbuffer size
|
||||||
|
|
||||||
GLsizei glWidth = (GLsizei)m_gl_context->GetBackBufferWidth();
|
GLsizei glWidth = (GLsizei)m_gl_context->GetBackBufferWidth();
|
||||||
|
@ -25,7 +25,7 @@ public:
|
|||||||
bool IsHeadless() const;
|
bool IsHeadless() const;
|
||||||
|
|
||||||
// Image to show, will be swapped immediately
|
// Image to show, will be swapped immediately
|
||||||
void ShowImage(AbstractTexture* image, const EFBRectangle& xfb_region);
|
void ShowImage(const AbstractTexture* image, const EFBRectangle& xfb_region);
|
||||||
|
|
||||||
static std::unique_ptr<SWOGLWindow> Create(const WindowSystemInfo& wsi);
|
static std::unique_ptr<SWOGLWindow> Create(const WindowSystemInfo& wsi);
|
||||||
|
|
||||||
|
@ -90,12 +90,10 @@ std::unique_ptr<AbstractPipeline> SWRenderer::CreatePipeline(const AbstractPipel
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Called on the GPU thread
|
// Called on the GPU thread
|
||||||
void SWRenderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region, u64 ticks)
|
void SWRenderer::RenderXFBToScreen(const AbstractTexture* texture, const EFBRectangle& xfb_region)
|
||||||
{
|
{
|
||||||
if (!IsHeadless())
|
if (!IsHeadless())
|
||||||
m_window->ShowImage(texture, xfb_region);
|
m_window->ShowImage(texture, xfb_region);
|
||||||
|
|
||||||
UpdateActiveConfig();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 SWRenderer::AccessEFB(EFBAccessType type, u32 x, u32 y, u32 InputData)
|
u32 SWRenderer::AccessEFB(EFBAccessType type, u32 x, u32 y, u32 InputData)
|
||||||
|
@ -39,7 +39,7 @@ public:
|
|||||||
|
|
||||||
TargetRectangle ConvertEFBRectangle(const EFBRectangle& rc) override;
|
TargetRectangle ConvertEFBRectangle(const EFBRectangle& rc) override;
|
||||||
|
|
||||||
void SwapImpl(AbstractTexture* texture, const EFBRectangle& rc, u64 ticks) override;
|
void RenderXFBToScreen(const AbstractTexture* texture, const EFBRectangle& rc) override;
|
||||||
|
|
||||||
void ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable,
|
void ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable,
|
||||||
u32 color, u32 z) override;
|
u32 color, u32 z) override;
|
||||||
|
@ -557,76 +557,24 @@ void Renderer::ReinterpretPixelData(unsigned int convtype)
|
|||||||
BindEFBToStateTracker();
|
BindEFBToStateTracker();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::SwapImpl(AbstractTexture* texture, const EFBRectangle& xfb_region, u64 ticks)
|
void Renderer::Flush()
|
||||||
{
|
{
|
||||||
// Pending/batched EFB pokes should be included in the final image.
|
Util::ExecuteCurrentCommandsAndRestoreState(true, false);
|
||||||
FramebufferManager::GetInstance()->FlushEFBPokes();
|
}
|
||||||
|
|
||||||
auto* xfb_texture = static_cast<VKTexture*>(texture);
|
void Renderer::BindBackbuffer(const ClearColor& clear_color)
|
||||||
|
{
|
||||||
// End the current render pass.
|
|
||||||
StateTracker::GetInstance()->EndRenderPass();
|
StateTracker::GetInstance()->EndRenderPass();
|
||||||
StateTracker::GetInstance()->OnEndFrame();
|
|
||||||
|
|
||||||
// Handle host window resizes.
|
// Handle host window resizes.
|
||||||
CheckForSurfaceChange();
|
CheckForSurfaceChange();
|
||||||
CheckForSurfaceResize();
|
CheckForSurfaceResize();
|
||||||
|
|
||||||
// There are a few variables which can alter the final window draw rectangle, and some of them
|
|
||||||
// are determined by guest state. Currently, the only way to catch these is to update every frame.
|
|
||||||
UpdateDrawRectangle();
|
|
||||||
|
|
||||||
// Ensure the worker thread is not still submitting a previous command buffer.
|
// Ensure the worker thread is not still submitting a previous command buffer.
|
||||||
// In other words, the last frame has been submitted (otherwise the next call would
|
// In other words, the last frame has been submitted (otherwise the next call would
|
||||||
// be a race, as the image may not have been consumed yet).
|
// be a race, as the image may not have been consumed yet).
|
||||||
g_command_buffer_mgr->PrepareToSubmitCommandBuffer();
|
g_command_buffer_mgr->PrepareToSubmitCommandBuffer();
|
||||||
|
|
||||||
// Draw to the screen if we have a swap chain.
|
|
||||||
if (m_swap_chain)
|
|
||||||
{
|
|
||||||
DrawScreen(xfb_texture, xfb_region);
|
|
||||||
|
|
||||||
// Submit the current command buffer, signaling rendering finished semaphore when it's done
|
|
||||||
// Because this final command buffer is rendering to the swap chain, we need to wait for
|
|
||||||
// the available semaphore to be signaled before executing the buffer. This final submission
|
|
||||||
// can happen off-thread in the background while we're preparing the next frame.
|
|
||||||
g_command_buffer_mgr->SubmitCommandBuffer(
|
|
||||||
true, m_image_available_semaphore, m_rendering_finished_semaphore,
|
|
||||||
m_swap_chain->GetSwapChain(), m_swap_chain->GetCurrentImageIndex());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// No swap chain, just execute command buffer.
|
|
||||||
g_command_buffer_mgr->SubmitCommandBuffer(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: It is important that no rendering calls are made to the EFB between submitting the
|
|
||||||
// (now-previous) frame and after the below config checks are completed. If the target size
|
|
||||||
// changes, as the resize methods to not defer the destruction of the framebuffer, the current
|
|
||||||
// command buffer will contain references to a now non-existent framebuffer.
|
|
||||||
|
|
||||||
// Prep for the next frame (get command buffer ready) before doing anything else.
|
|
||||||
BeginFrame();
|
|
||||||
|
|
||||||
// Restore the EFB color texture to color attachment ready for rendering the next frame.
|
|
||||||
FramebufferManager::GetInstance()->GetEFBColorTexture()->TransitionToLayout(
|
|
||||||
g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
|
|
||||||
RestoreAPIState();
|
|
||||||
|
|
||||||
// Determine what (if anything) has changed in the config.
|
|
||||||
CheckForConfigChanges();
|
|
||||||
|
|
||||||
// Clean up stale textures.
|
|
||||||
TextureCache::GetInstance()->Cleanup(frameCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Renderer::Flush()
|
|
||||||
{
|
|
||||||
Util::ExecuteCurrentCommandsAndRestoreState(true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Renderer::DrawScreen(VKTexture* xfb_texture, const EFBRectangle& xfb_region)
|
|
||||||
{
|
|
||||||
VkResult res;
|
VkResult res;
|
||||||
if (!g_command_buffer_mgr->CheckLastPresentFail())
|
if (!g_command_buffer_mgr->CheckLastPresentFail())
|
||||||
{
|
{
|
||||||
@ -676,48 +624,65 @@ void Renderer::DrawScreen(VKTexture* xfb_texture, const EFBRectangle& xfb_region
|
|||||||
// Begin render pass for rendering to the swap chain.
|
// Begin render pass for rendering to the swap chain.
|
||||||
VkClearValue clear_value = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
|
VkClearValue clear_value = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
|
||||||
StateTracker::GetInstance()->BeginClearRenderPass(region, &clear_value, 1);
|
StateTracker::GetInstance()->BeginClearRenderPass(region, &clear_value, 1);
|
||||||
|
}
|
||||||
|
|
||||||
// Draw
|
void Renderer::PresentBackbuffer()
|
||||||
BlitScreen(m_swap_chain_render_pass, GetTargetRectangle(), xfb_region,
|
{
|
||||||
xfb_texture->GetRawTexIdentifier());
|
|
||||||
|
|
||||||
// Draw OSD
|
|
||||||
SetViewport(0.0f, 0.0f, static_cast<float>(backbuffer->GetWidth()),
|
|
||||||
static_cast<float>(backbuffer->GetHeight()), 0.0f, 1.0f);
|
|
||||||
StateTracker::GetInstance()->SetPendingRebind();
|
|
||||||
DrawImGui();
|
|
||||||
|
|
||||||
// End drawing to backbuffer
|
// End drawing to backbuffer
|
||||||
StateTracker::GetInstance()->EndRenderPass();
|
StateTracker::GetInstance()->EndRenderPass();
|
||||||
|
StateTracker::GetInstance()->OnEndFrame();
|
||||||
|
|
||||||
// Transition the backbuffer to PRESENT_SRC to ensure all commands drawing
|
// Transition the backbuffer to PRESENT_SRC to ensure all commands drawing
|
||||||
// to it have finished before present.
|
// to it have finished before present.
|
||||||
|
Texture2D* backbuffer = m_swap_chain->GetCurrentTexture();
|
||||||
backbuffer->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
backbuffer->TransitionToLayout(g_command_buffer_mgr->GetCurrentCommandBuffer(),
|
||||||
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
|
||||||
|
|
||||||
|
// Submit the current command buffer, signaling rendering finished semaphore when it's done
|
||||||
|
// Because this final command buffer is rendering to the swap chain, we need to wait for
|
||||||
|
// the available semaphore to be signaled before executing the buffer. This final submission
|
||||||
|
// can happen off-thread in the background while we're preparing the next frame.
|
||||||
|
g_command_buffer_mgr->SubmitCommandBuffer(
|
||||||
|
true, m_image_available_semaphore, m_rendering_finished_semaphore,
|
||||||
|
m_swap_chain->GetSwapChain(), m_swap_chain->GetCurrentImageIndex());
|
||||||
|
|
||||||
|
BeginFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::BlitScreen(VkRenderPass render_pass, const TargetRectangle& dst_rect,
|
void Renderer::RenderXFBToScreen(const AbstractTexture* texture, const EFBRectangle& rc)
|
||||||
const TargetRectangle& src_rect, const Texture2D* src_tex)
|
|
||||||
{
|
{
|
||||||
|
const TargetRectangle target_rc = GetTargetRectangle();
|
||||||
|
|
||||||
VulkanPostProcessing* post_processor = static_cast<VulkanPostProcessing*>(m_post_processor.get());
|
VulkanPostProcessing* post_processor = static_cast<VulkanPostProcessing*>(m_post_processor.get());
|
||||||
if (g_ActiveConfig.stereo_mode == StereoMode::SBS ||
|
if (g_ActiveConfig.stereo_mode == StereoMode::SBS ||
|
||||||
g_ActiveConfig.stereo_mode == StereoMode::TAB)
|
g_ActiveConfig.stereo_mode == StereoMode::TAB)
|
||||||
{
|
{
|
||||||
TargetRectangle left_rect;
|
TargetRectangle left_rect;
|
||||||
TargetRectangle right_rect;
|
TargetRectangle right_rect;
|
||||||
std::tie(left_rect, right_rect) = ConvertStereoRectangle(dst_rect);
|
std::tie(left_rect, right_rect) = ConvertStereoRectangle(target_rc);
|
||||||
|
|
||||||
post_processor->BlitFromTexture(left_rect, src_rect, src_tex, 0, render_pass);
|
post_processor->BlitFromTexture(left_rect, rc,
|
||||||
post_processor->BlitFromTexture(right_rect, src_rect, src_tex, 1, render_pass);
|
static_cast<const VKTexture*>(texture)->GetRawTexIdentifier(),
|
||||||
|
0, m_swap_chain_render_pass);
|
||||||
|
post_processor->BlitFromTexture(right_rect, rc,
|
||||||
|
static_cast<const VKTexture*>(texture)->GetRawTexIdentifier(),
|
||||||
|
1, m_swap_chain_render_pass);
|
||||||
}
|
}
|
||||||
else if (g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer)
|
else if (g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer)
|
||||||
{
|
{
|
||||||
post_processor->BlitFromTexture(dst_rect, src_rect, src_tex, -1, render_pass);
|
post_processor->BlitFromTexture(target_rc, rc,
|
||||||
|
static_cast<const VKTexture*>(texture)->GetRawTexIdentifier(),
|
||||||
|
-1, m_swap_chain_render_pass);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
post_processor->BlitFromTexture(dst_rect, src_rect, src_tex, 0, render_pass);
|
post_processor->BlitFromTexture(target_rc, rc,
|
||||||
|
static_cast<const VKTexture*>(texture)->GetRawTexIdentifier(),
|
||||||
|
0, m_swap_chain_render_pass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The post-processor uses the old-style Vulkan draws, which mess with the tracked state.
|
||||||
|
StateTracker::GetInstance()->SetPendingRebind();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::CheckForSurfaceChange()
|
void Renderer::CheckForSurfaceChange()
|
||||||
@ -766,36 +731,20 @@ void Renderer::CheckForSurfaceResize()
|
|||||||
OnSwapChainResized();
|
OnSwapChainResized();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::CheckForConfigChanges()
|
void Renderer::OnConfigChanged(u32 bits)
|
||||||
{
|
{
|
||||||
// Save the video config so we can compare against to determine which settings have changed.
|
|
||||||
const u32 old_multisamples = g_ActiveConfig.iMultisamples;
|
|
||||||
const int old_anisotropy = g_ActiveConfig.iMaxAnisotropy;
|
|
||||||
const bool old_force_filtering = g_ActiveConfig.bForceFiltering;
|
|
||||||
|
|
||||||
// Copy g_Config to g_ActiveConfig.
|
|
||||||
// NOTE: This can potentially race with the UI thread, however if it does, the changes will be
|
|
||||||
// delayed until the next time CheckForConfigChanges is called.
|
|
||||||
UpdateActiveConfig();
|
|
||||||
|
|
||||||
// Determine which (if any) settings have changed.
|
|
||||||
const bool multisamples_changed = old_multisamples != g_ActiveConfig.iMultisamples;
|
|
||||||
const bool anisotropy_changed = old_anisotropy != g_ActiveConfig.iMaxAnisotropy;
|
|
||||||
const bool force_texture_filtering_changed =
|
|
||||||
old_force_filtering != g_ActiveConfig.bForceFiltering;
|
|
||||||
|
|
||||||
// Update texture cache settings with any changed options.
|
// Update texture cache settings with any changed options.
|
||||||
TextureCache::GetInstance()->OnConfigChanged(g_ActiveConfig);
|
TextureCache::GetInstance()->OnConfigChanged(g_ActiveConfig);
|
||||||
|
|
||||||
// Handle settings that can cause the EFB framebuffer to change.
|
// Handle settings that can cause the EFB framebuffer to change.
|
||||||
if (CalculateTargetSize() || multisamples_changed)
|
if (bits & CONFIG_CHANGE_BIT_TARGET_SIZE)
|
||||||
RecreateEFBFramebuffer();
|
RecreateEFBFramebuffer();
|
||||||
|
|
||||||
// MSAA samples changed, we need to recreate the EFB render pass.
|
// MSAA samples changed, we need to recreate the EFB render pass.
|
||||||
// If the stereoscopy mode changed, we need to recreate the buffers as well.
|
// If the stereoscopy mode changed, we need to recreate the buffers as well.
|
||||||
// SSAA changed on/off, we have to recompile shaders.
|
// SSAA changed on/off, we have to recompile shaders.
|
||||||
// Changing stereoscopy from off<->on also requires shaders to be recompiled.
|
// Changing stereoscopy from off<->on also requires shaders to be recompiled.
|
||||||
if (CheckForHostConfigChanges())
|
if (bits & (CONFIG_CHANGE_BIT_HOST_CONFIG | CONFIG_CHANGE_BIT_MULTISAMPLES))
|
||||||
{
|
{
|
||||||
RecreateEFBFramebuffer();
|
RecreateEFBFramebuffer();
|
||||||
RecompileShaders();
|
RecompileShaders();
|
||||||
@ -805,22 +754,21 @@ void Renderer::CheckForConfigChanges()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For vsync, we need to change the present mode, which means recreating the swap chain.
|
// For vsync, we need to change the present mode, which means recreating the swap chain.
|
||||||
if (m_swap_chain && g_ActiveConfig.IsVSync() != m_swap_chain->IsVSyncEnabled())
|
if (m_swap_chain && bits & CONFIG_CHANGE_BIT_VSYNC)
|
||||||
{
|
{
|
||||||
g_command_buffer_mgr->WaitForGPUIdle();
|
g_command_buffer_mgr->WaitForGPUIdle();
|
||||||
m_swap_chain->SetVSync(g_ActiveConfig.IsVSync());
|
m_swap_chain->SetVSync(g_ActiveConfig.IsVSync());
|
||||||
}
|
}
|
||||||
|
|
||||||
// For quad-buffered stereo we need to change the layer count, so recreate the swap chain.
|
// For quad-buffered stereo we need to change the layer count, so recreate the swap chain.
|
||||||
if (m_swap_chain &&
|
if (m_swap_chain && bits & CONFIG_CHANGE_BIT_STEREO_MODE)
|
||||||
(g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer) != m_swap_chain->IsStereoEnabled())
|
|
||||||
{
|
{
|
||||||
g_command_buffer_mgr->WaitForGPUIdle();
|
g_command_buffer_mgr->WaitForGPUIdle();
|
||||||
m_swap_chain->RecreateSwapChain();
|
m_swap_chain->RecreateSwapChain();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wipe sampler cache if force texture filtering or anisotropy changes.
|
// Wipe sampler cache if force texture filtering or anisotropy changes.
|
||||||
if (anisotropy_changed || force_texture_filtering_changed)
|
if (bits & (CONFIG_CHANGE_BIT_ANISOTROPY | CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING))
|
||||||
ResetSamplerStates();
|
ResetSamplerStates();
|
||||||
|
|
||||||
// Check for a changed post-processing shader and recompile if needed.
|
// Check for a changed post-processing shader and recompile if needed.
|
||||||
|
@ -60,8 +60,9 @@ public:
|
|||||||
void BBoxWrite(int index, u16 value) override;
|
void BBoxWrite(int index, u16 value) override;
|
||||||
TargetRectangle ConvertEFBRectangle(const EFBRectangle& rc) override;
|
TargetRectangle ConvertEFBRectangle(const EFBRectangle& rc) override;
|
||||||
|
|
||||||
void SwapImpl(AbstractTexture* texture, const EFBRectangle& rc, u64 ticks) override;
|
|
||||||
void Flush() override;
|
void Flush() override;
|
||||||
|
void RenderXFBToScreen(const AbstractTexture* texture, const EFBRectangle& rc) override;
|
||||||
|
void OnConfigChanged(u32 bits) override;
|
||||||
|
|
||||||
void ClearScreen(const EFBRectangle& rc, bool color_enable, bool alpha_enable, bool z_enable,
|
void ClearScreen(const EFBRectangle& rc, bool color_enable, bool alpha_enable, bool z_enable,
|
||||||
u32 color, u32 z) override;
|
u32 color, u32 z) override;
|
||||||
@ -88,6 +89,8 @@ public:
|
|||||||
float far_depth) override;
|
float far_depth) override;
|
||||||
void Draw(u32 base_vertex, u32 num_vertices) override;
|
void Draw(u32 base_vertex, u32 num_vertices) override;
|
||||||
void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) override;
|
void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) override;
|
||||||
|
void BindBackbuffer(const ClearColor& clear_color = {}) override;
|
||||||
|
void PresentBackbuffer() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool CreateSemaphores();
|
bool CreateSemaphores();
|
||||||
@ -97,7 +100,6 @@ private:
|
|||||||
|
|
||||||
void CheckForSurfaceChange();
|
void CheckForSurfaceChange();
|
||||||
void CheckForSurfaceResize();
|
void CheckForSurfaceResize();
|
||||||
void CheckForConfigChanges();
|
|
||||||
|
|
||||||
void ResetSamplerStates();
|
void ResetSamplerStates();
|
||||||
|
|
||||||
@ -110,13 +112,6 @@ private:
|
|||||||
bool CompileShaders();
|
bool CompileShaders();
|
||||||
void DestroyShaders();
|
void DestroyShaders();
|
||||||
|
|
||||||
// Draw the frame, as well as the OSD to the swap chain.
|
|
||||||
void DrawScreen(VKTexture* xfb_texture, const EFBRectangle& xfb_region);
|
|
||||||
|
|
||||||
// Copies/scales an image to the currently-bound framebuffer.
|
|
||||||
void BlitScreen(VkRenderPass render_pass, const TargetRectangle& dst_rect,
|
|
||||||
const TargetRectangle& src_rect, const Texture2D* src_tex);
|
|
||||||
|
|
||||||
std::tuple<VkBuffer, u32> UpdateUtilityUniformBuffer(const void* uniforms, u32 uniforms_size);
|
std::tuple<VkBuffer, u32> UpdateUtilityUniformBuffer(const void* uniforms, u32 uniforms_size);
|
||||||
|
|
||||||
VkSemaphore m_image_available_semaphore = VK_NULL_HANDLE;
|
VkSemaphore m_image_available_semaphore = VK_NULL_HANDLE;
|
||||||
|
@ -91,9 +91,6 @@ Renderer::Renderer(int backbuffer_width, int backbuffer_height, float backbuffer
|
|||||||
CalculateTargetSize();
|
CalculateTargetSize();
|
||||||
|
|
||||||
m_aspect_wide = SConfig::GetInstance().bWii && Config::Get(Config::SYSCONF_WIDESCREEN);
|
m_aspect_wide = SConfig::GetInstance().bWii && Config::Get(Config::SYSCONF_WIDESCREEN);
|
||||||
|
|
||||||
m_last_host_config_bits = ShaderHostConfig::GetCurrent().bits;
|
|
||||||
m_last_efb_multisamples = g_ActiveConfig.iMultisamples;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Renderer::~Renderer() = default;
|
Renderer::~Renderer() = default;
|
||||||
@ -239,24 +236,56 @@ void Renderer::SaveScreenshot(const std::string& filename, bool wait_for_complet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Renderer::CheckForHostConfigChanges()
|
void Renderer::CheckForConfigChanges()
|
||||||
{
|
{
|
||||||
|
const ShaderHostConfig old_shader_host_config = ShaderHostConfig::GetCurrent();
|
||||||
|
const StereoMode old_stereo = g_ActiveConfig.stereo_mode;
|
||||||
|
const u32 old_multisamples = g_ActiveConfig.iMultisamples;
|
||||||
|
const int old_anisotropy = g_ActiveConfig.iMaxAnisotropy;
|
||||||
|
const bool old_force_filtering = g_ActiveConfig.bForceFiltering;
|
||||||
|
const bool old_vsync = g_ActiveConfig.IsVSync();
|
||||||
|
const bool old_bbox = g_ActiveConfig.bBBoxEnable;
|
||||||
|
|
||||||
|
UpdateActiveConfig();
|
||||||
|
|
||||||
|
// Update texture cache settings with any changed options.
|
||||||
|
g_texture_cache->OnConfigChanged(g_ActiveConfig);
|
||||||
|
|
||||||
|
// Determine which (if any) settings have changed.
|
||||||
ShaderHostConfig new_host_config = ShaderHostConfig::GetCurrent();
|
ShaderHostConfig new_host_config = ShaderHostConfig::GetCurrent();
|
||||||
if (new_host_config.bits == m_last_host_config_bits &&
|
u32 changed_bits = 0;
|
||||||
m_last_efb_multisamples == g_ActiveConfig.iMultisamples)
|
if (old_shader_host_config.bits != new_host_config.bits)
|
||||||
|
changed_bits |= CONFIG_CHANGE_BIT_HOST_CONFIG;
|
||||||
|
if (old_stereo != g_ActiveConfig.stereo_mode)
|
||||||
|
changed_bits |= CONFIG_CHANGE_BIT_STEREO_MODE;
|
||||||
|
if (old_multisamples != g_ActiveConfig.iMultisamples)
|
||||||
|
changed_bits |= CONFIG_CHANGE_BIT_MULTISAMPLES;
|
||||||
|
if (old_anisotropy != g_ActiveConfig.iMaxAnisotropy)
|
||||||
|
changed_bits |= CONFIG_CHANGE_BIT_ANISOTROPY;
|
||||||
|
if (old_force_filtering != g_ActiveConfig.bForceFiltering)
|
||||||
|
changed_bits |= CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING;
|
||||||
|
if (old_vsync != g_ActiveConfig.IsVSync())
|
||||||
|
changed_bits |= CONFIG_CHANGE_BIT_VSYNC;
|
||||||
|
if (old_bbox != g_ActiveConfig.bBBoxEnable)
|
||||||
|
changed_bits |= CONFIG_CHANGE_BIT_BBOX;
|
||||||
|
if (CalculateTargetSize())
|
||||||
|
changed_bits |= CONFIG_CHANGE_BIT_TARGET_SIZE;
|
||||||
|
|
||||||
|
// No changes?
|
||||||
|
if (changed_bits == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Notify the backend of the changes, if any.
|
||||||
|
OnConfigChanged(changed_bits);
|
||||||
|
|
||||||
|
// Reload shaders if host config has changed.
|
||||||
|
if (changed_bits & (CONFIG_CHANGE_BIT_HOST_CONFIG | CONFIG_CHANGE_BIT_MULTISAMPLES))
|
||||||
{
|
{
|
||||||
return false;
|
OSD::AddMessage("Video config changed, reloading shaders.", OSD::Duration::NORMAL);
|
||||||
|
SetPipeline(nullptr);
|
||||||
|
g_vertex_manager->InvalidatePipelineObject();
|
||||||
|
g_shader_cache->SetHostConfig(new_host_config, g_ActiveConfig.iMultisamples);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_last_host_config_bits = new_host_config.bits;
|
|
||||||
m_last_efb_multisamples = g_ActiveConfig.iMultisamples;
|
|
||||||
|
|
||||||
// Reload shaders.
|
|
||||||
OSD::AddMessage("Video config changed, reloading shaders.", OSD::Duration::NORMAL);
|
|
||||||
SetPipeline(nullptr);
|
|
||||||
g_vertex_manager->InvalidatePipelineObject();
|
|
||||||
g_shader_cache->SetHostConfig(new_host_config, g_ActiveConfig.iMultisamples);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create On-Screen-Messages
|
// Create On-Screen-Messages
|
||||||
@ -754,6 +783,8 @@ void Renderer::ShutdownImGui()
|
|||||||
|
|
||||||
void Renderer::BeginImGuiFrame()
|
void Renderer::BeginImGuiFrame()
|
||||||
{
|
{
|
||||||
|
std::unique_lock<std::mutex> imgui_lock(m_imgui_mutex);
|
||||||
|
|
||||||
const u64 current_time_us = Common::Timer::GetTimeUs();
|
const u64 current_time_us = Common::Timer::GetTimeUs();
|
||||||
const u64 time_diff_us = current_time_us - m_imgui_last_frame_time;
|
const u64 time_diff_us = current_time_us - m_imgui_last_frame_time;
|
||||||
const float time_diff_secs = static_cast<float>(time_diff_us / 1000000.0);
|
const float time_diff_secs = static_cast<float>(time_diff_us / 1000000.0);
|
||||||
@ -768,12 +799,17 @@ void Renderer::BeginImGuiFrame()
|
|||||||
ImGui::NewFrame();
|
ImGui::NewFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::DrawImGui()
|
void Renderer::RenderImGui()
|
||||||
{
|
{
|
||||||
|
ImGui::Render();
|
||||||
|
|
||||||
ImDrawData* draw_data = ImGui::GetDrawData();
|
ImDrawData* draw_data = ImGui::GetDrawData();
|
||||||
if (!draw_data)
|
if (!draw_data)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
SetViewport(0.0f, 0.0f, static_cast<float>(m_backbuffer_width),
|
||||||
|
static_cast<float>(m_backbuffer_height), 0.0f, 1.0f);
|
||||||
|
|
||||||
// Uniform buffer for draws.
|
// Uniform buffer for draws.
|
||||||
struct ImGuiUbo
|
struct ImGuiUbo
|
||||||
{
|
{
|
||||||
@ -783,8 +819,9 @@ void Renderer::DrawImGui()
|
|||||||
ImGuiUbo ubo = {{1.0f / m_backbuffer_width * 2.0f, 1.0f / m_backbuffer_height * 2.0f}};
|
ImGuiUbo ubo = {{1.0f / m_backbuffer_width * 2.0f, 1.0f / m_backbuffer_height * 2.0f}};
|
||||||
|
|
||||||
// Set up common state for drawing.
|
// Set up common state for drawing.
|
||||||
g_vertex_manager->UploadUtilityUniforms(&ubo, sizeof(ubo));
|
SetPipeline(m_imgui_pipeline.get());
|
||||||
SetSamplerState(0, RenderState::GetPointSamplerState());
|
SetSamplerState(0, RenderState::GetPointSamplerState());
|
||||||
|
g_vertex_manager->UploadUtilityUniforms(&ubo, sizeof(ubo));
|
||||||
|
|
||||||
for (int i = 0; i < draw_data->CmdListsCount; i++)
|
for (int i = 0; i < draw_data->CmdListsCount; i++)
|
||||||
{
|
{
|
||||||
@ -805,7 +842,6 @@ void Renderer::DrawImGui()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetPipeline(m_imgui_pipeline.get());
|
|
||||||
SetScissorRect(MathUtil::Rectangle<int>(
|
SetScissorRect(MathUtil::Rectangle<int>(
|
||||||
static_cast<int>(cmd.ClipRect.x), static_cast<int>(cmd.ClipRect.y),
|
static_cast<int>(cmd.ClipRect.x), static_cast<int>(cmd.ClipRect.y),
|
||||||
static_cast<int>(cmd.ClipRect.z), static_cast<int>(cmd.ClipRect.w)));
|
static_cast<int>(cmd.ClipRect.z), static_cast<int>(cmd.ClipRect.w)));
|
||||||
@ -821,13 +857,31 @@ std::unique_lock<std::mutex> Renderer::GetImGuiLock()
|
|||||||
return std::unique_lock<std::mutex>(m_imgui_mutex);
|
return std::unique_lock<std::mutex>(m_imgui_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Renderer::BeginUIFrame()
|
||||||
|
{
|
||||||
|
ResetAPIState();
|
||||||
|
BindBackbuffer({0.0f, 0.0f, 0.0f, 1.0f});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::EndUIFrame()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
auto lock = GetImGuiLock();
|
||||||
|
RenderImGui();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> guard(m_swap_mutex);
|
||||||
|
PresentBackbuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
BeginImGuiFrame();
|
||||||
|
RestoreAPIState();
|
||||||
|
}
|
||||||
|
|
||||||
void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const EFBRectangle& rc,
|
void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const EFBRectangle& rc,
|
||||||
u64 ticks)
|
u64 ticks)
|
||||||
{
|
{
|
||||||
// Hold the imgui lock while we're presenting.
|
|
||||||
// It's only to prevent races on inputs anyway, at this point.
|
|
||||||
std::unique_lock<std::mutex> imgui_lock(m_imgui_mutex);
|
|
||||||
|
|
||||||
const AspectMode suggested = g_ActiveConfig.suggested_aspect_mode;
|
const AspectMode suggested = g_ActiveConfig.suggested_aspect_mode;
|
||||||
if (suggested == AspectMode::Analog || suggested == AspectMode::AnalogWide)
|
if (suggested == AspectMode::Analog || suggested == AspectMode::AnalogWide)
|
||||||
{
|
{
|
||||||
@ -892,16 +946,27 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
|
|||||||
// with the loader, and it has not been unmapped yet. Force a pipeline flush to avoid this.
|
// with the loader, and it has not been unmapped yet. Force a pipeline flush to avoid this.
|
||||||
g_vertex_manager->Flush();
|
g_vertex_manager->Flush();
|
||||||
|
|
||||||
// Draw any imgui overlays we have. Note that "draw" here means "create commands", the actual
|
// Render the XFB to the screen.
|
||||||
// draw calls don't get issued until DrawImGui is called, which happens in SwapImpl.
|
ResetAPIState();
|
||||||
DrawDebugText();
|
BindBackbuffer({0.0f, 0.0f, 0.0f, 1.0f});
|
||||||
OSD::DrawMessages();
|
UpdateDrawRectangle();
|
||||||
ImGui::Render();
|
RenderXFBToScreen(xfb_entry->texture.get(), xfb_rect);
|
||||||
|
|
||||||
// TODO: merge more generic parts into VideoCommon
|
// Hold the imgui lock while we're presenting.
|
||||||
|
// It's only to prevent races on inputs anyway, at this point.
|
||||||
|
{
|
||||||
|
auto lock = GetImGuiLock();
|
||||||
|
|
||||||
|
DrawDebugText();
|
||||||
|
OSD::DrawMessages();
|
||||||
|
|
||||||
|
RenderImGui();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present to the window system.
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> guard(m_swap_mutex);
|
std::lock_guard<std::mutex> guard(m_swap_mutex);
|
||||||
g_renderer->SwapImpl(xfb_entry->texture.get(), xfb_rect, ticks);
|
PresentBackbuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the window size based on the frame that was just rendered.
|
// Update the window size based on the frame that was just rendered.
|
||||||
@ -923,10 +988,9 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
|
|||||||
GFX_DEBUGGER_PAUSE_AT(NEXT_FRAME, true);
|
GFX_DEBUGGER_PAUSE_AT(NEXT_FRAME, true);
|
||||||
|
|
||||||
// Begin new frame
|
// Begin new frame
|
||||||
// Set default viewport and scissor, for the clear to work correctly
|
|
||||||
// New frame
|
|
||||||
stats.ResetFrame();
|
stats.ResetFrame();
|
||||||
g_shader_cache->RetrieveAsyncShaders();
|
g_shader_cache->RetrieveAsyncShaders();
|
||||||
|
BeginImGuiFrame();
|
||||||
|
|
||||||
// We invalidate the pipeline object at the start of the frame.
|
// We invalidate the pipeline object at the start of the frame.
|
||||||
// This is for the rare case where only a single pipeline configuration is used,
|
// This is for the rare case where only a single pipeline configuration is used,
|
||||||
@ -938,7 +1002,14 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const
|
|||||||
// rate and not waiting for vblank. Otherwise, we'd end up with a huge list of pending copies.
|
// rate and not waiting for vblank. Otherwise, we'd end up with a huge list of pending copies.
|
||||||
g_texture_cache->FlushEFBCopies();
|
g_texture_cache->FlushEFBCopies();
|
||||||
|
|
||||||
BeginImGuiFrame();
|
// Remove stale EFB/XFB copies.
|
||||||
|
g_texture_cache->Cleanup(frameCount);
|
||||||
|
|
||||||
|
// Handle any config changes, this gets propogated to the backend.
|
||||||
|
CheckForConfigChanges();
|
||||||
|
g_Config.iSaveTargetId = 0;
|
||||||
|
|
||||||
|
RestoreAPIState();
|
||||||
|
|
||||||
Core::Callback_VideoCopiedToXFB(true);
|
Core::Callback_VideoCopiedToXFB(true);
|
||||||
}
|
}
|
||||||
|
@ -109,6 +109,14 @@ public:
|
|||||||
virtual void Draw(u32 base_vertex, u32 num_vertices) {}
|
virtual void Draw(u32 base_vertex, u32 num_vertices) {}
|
||||||
virtual void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) {}
|
virtual void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) {}
|
||||||
|
|
||||||
|
// Binds the backbuffer for rendering. The buffer will be cleared immediately after binding.
|
||||||
|
// This is where any window size changes are detected, therefore m_backbuffer_width and/or
|
||||||
|
// m_backbuffer_height may change after this function returns.
|
||||||
|
virtual void BindBackbuffer(const ClearColor& clear_color = {}) {}
|
||||||
|
|
||||||
|
// Presents the backbuffer to the window system, or "swaps buffers".
|
||||||
|
virtual void PresentBackbuffer() {}
|
||||||
|
|
||||||
// Shader modules/objects.
|
// Shader modules/objects.
|
||||||
virtual std::unique_ptr<AbstractShader>
|
virtual std::unique_ptr<AbstractShader>
|
||||||
CreateShaderFromSource(ShaderStage stage, const char* source, size_t length) = 0;
|
CreateShaderFromSource(ShaderStage stage, const char* source, size_t length) = 0;
|
||||||
@ -173,11 +181,18 @@ public:
|
|||||||
virtual u16 BBoxRead(int index) = 0;
|
virtual u16 BBoxRead(int index) = 0;
|
||||||
virtual void BBoxWrite(int index, u16 value) = 0;
|
virtual void BBoxWrite(int index, u16 value) = 0;
|
||||||
|
|
||||||
|
virtual void Flush() {}
|
||||||
|
|
||||||
// Finish up the current frame, print some stats
|
// Finish up the current frame, print some stats
|
||||||
void Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const EFBRectangle& rc,
|
void Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const EFBRectangle& rc,
|
||||||
u64 ticks);
|
u64 ticks);
|
||||||
virtual void SwapImpl(AbstractTexture* texture, const EFBRectangle& rc, u64 ticks) = 0;
|
|
||||||
virtual void Flush() {}
|
// Draws the specified XFB buffer to the screen, performing any post-processing.
|
||||||
|
// Assumes that the backbuffer has already been bound and cleared.
|
||||||
|
virtual void RenderXFBToScreen(const AbstractTexture* texture, const EFBRectangle& rc) {}
|
||||||
|
|
||||||
|
// Called when the configuration changes, and backend structures need to be updated.
|
||||||
|
virtual void OnConfigChanged(u32 bits) {}
|
||||||
|
|
||||||
PEControl::PixelFormat GetPrevPixelFormat() const { return m_prev_efb_format; }
|
PEControl::PixelFormat GetPrevPixelFormat() const { return m_prev_efb_format; }
|
||||||
void StorePixelFormat(PEControl::PixelFormat new_format) { m_prev_efb_format = new_format; }
|
void StorePixelFormat(PEControl::PixelFormat new_format) { m_prev_efb_format = new_format; }
|
||||||
@ -195,23 +210,43 @@ public:
|
|||||||
// as the drawing is tied to a "frame".
|
// as the drawing is tied to a "frame".
|
||||||
std::unique_lock<std::mutex> GetImGuiLock();
|
std::unique_lock<std::mutex> GetImGuiLock();
|
||||||
|
|
||||||
|
// Begins/presents a "UI frame". UI frames do not draw any of the console XFB, but this could
|
||||||
|
// change in the future.
|
||||||
|
void BeginUIFrame();
|
||||||
|
void EndUIFrame();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
// Bitmask containing information about which configuration has changed for the backend.
|
||||||
|
enum ConfigChangeBits : u32
|
||||||
|
{
|
||||||
|
CONFIG_CHANGE_BIT_HOST_CONFIG = (1 << 0),
|
||||||
|
CONFIG_CHANGE_BIT_MULTISAMPLES = (1 << 1),
|
||||||
|
CONFIG_CHANGE_BIT_STEREO_MODE = (1 << 2),
|
||||||
|
CONFIG_CHANGE_BIT_TARGET_SIZE = (1 << 3),
|
||||||
|
CONFIG_CHANGE_BIT_ANISOTROPY = (1 << 4),
|
||||||
|
CONFIG_CHANGE_BIT_FORCE_TEXTURE_FILTERING = (1 << 5),
|
||||||
|
CONFIG_CHANGE_BIT_VSYNC = (1 << 6),
|
||||||
|
CONFIG_CHANGE_BIT_BBOX = (1 << 7)
|
||||||
|
};
|
||||||
|
|
||||||
std::tuple<int, int> CalculateTargetScale(int x, int y) const;
|
std::tuple<int, int> CalculateTargetScale(int x, int y) const;
|
||||||
bool CalculateTargetSize();
|
bool CalculateTargetSize();
|
||||||
|
|
||||||
bool CheckForHostConfigChanges();
|
void CheckForConfigChanges();
|
||||||
|
|
||||||
void CheckFifoRecording();
|
void CheckFifoRecording();
|
||||||
void RecordVideoMemory();
|
void RecordVideoMemory();
|
||||||
|
|
||||||
// Renders ImGui windows to the currently-bound framebuffer.
|
// Sets up ImGui state for the next frame.
|
||||||
void DrawImGui();
|
// This function itself acquires the ImGui lock, so it should not be held.
|
||||||
|
void BeginImGuiFrame();
|
||||||
|
|
||||||
// Destroys all ImGui GPU resources, must do before shutdown.
|
// Destroys all ImGui GPU resources, must do before shutdown.
|
||||||
void ShutdownImGui();
|
void ShutdownImGui();
|
||||||
|
|
||||||
// Sets up ImGui state for the next frame.
|
// Renders ImGui windows to the currently-bound framebuffer.
|
||||||
void BeginImGuiFrame();
|
// Should be called with the ImGui lock held.
|
||||||
|
void RenderImGui();
|
||||||
|
|
||||||
// TODO: Remove the width/height parameters once we make the EFB an abstract framebuffer.
|
// TODO: Remove the width/height parameters once we make the EFB an abstract framebuffer.
|
||||||
const AbstractFramebuffer* m_current_framebuffer = nullptr;
|
const AbstractFramebuffer* m_current_framebuffer = nullptr;
|
||||||
@ -244,9 +279,6 @@ protected:
|
|||||||
Common::Flag m_surface_resized;
|
Common::Flag m_surface_resized;
|
||||||
std::mutex m_swap_mutex;
|
std::mutex m_swap_mutex;
|
||||||
|
|
||||||
u32 m_last_host_config_bits = 0;
|
|
||||||
u32 m_last_efb_multisamples = 1;
|
|
||||||
|
|
||||||
// ImGui resources.
|
// ImGui resources.
|
||||||
std::unique_ptr<NativeVertexFormat> m_imgui_vertex_format;
|
std::unique_ptr<NativeVertexFormat> m_imgui_vertex_format;
|
||||||
std::vector<std::unique_ptr<AbstractTexture>> m_imgui_textures;
|
std::vector<std::unique_ptr<AbstractTexture>> m_imgui_textures;
|
||||||
|
Loading…
Reference in New Issue
Block a user