diff options
| author | Tilka <tilkax@gmail.com> | 2019-02-26 04:21:05 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-02-26 04:21:05 +0000 |
| commit | 6ce7f44b8a0bb3b95d3f12ebffbef7a71a443fdd (patch) | |
| tree | 98380ce3a3284c52597a6fba86589c847219ecd5 /Source/Core/VideoCommon | |
| parent | 19673ce79af814ca3b77d7f8ea60458e564bfa51 (diff) | |
| parent | f039149198657c1891e1c6462ed30c31ed4b8486 (diff) | |
Merge pull request #7753 from stenzek/videocommon-all-the-things
Move a significant amount of video backend logic to VideoCommon
Diffstat (limited to 'Source/Core/VideoCommon')
46 files changed, 4105 insertions, 1257 deletions
diff --git a/Source/Core/VideoCommon/AbstractFramebuffer.cpp b/Source/Core/VideoCommon/AbstractFramebuffer.cpp index f8a9b07ba8..c6a1693788 100644 --- a/Source/Core/VideoCommon/AbstractFramebuffer.cpp +++ b/Source/Core/VideoCommon/AbstractFramebuffer.cpp @@ -5,10 +5,13 @@ #include "VideoCommon/AbstractFramebuffer.h" #include "VideoCommon/AbstractTexture.h" -AbstractFramebuffer::AbstractFramebuffer(AbstractTextureFormat color_format, +AbstractFramebuffer::AbstractFramebuffer(AbstractTexture* color_attachment, + AbstractTexture* depth_attachment, + AbstractTextureFormat color_format, AbstractTextureFormat depth_format, u32 width, u32 height, u32 layers, u32 samples) - : m_color_format(color_format), m_depth_format(depth_format), m_width(width), m_height(height), + : m_color_attachment(color_attachment), m_depth_attachment(depth_attachment), + m_color_format(color_format), m_depth_format(depth_format), m_width(width), m_height(height), m_layers(layers), m_samples(samples) { } @@ -26,7 +29,7 @@ bool AbstractFramebuffer::ValidateConfig(const AbstractTexture* color_attachment // MSAA textures are not supported with mip levels on most backends, and it simplifies our // handling of framebuffers. auto CheckAttachment = [](const AbstractTexture* tex) { - return tex->GetConfig().rendertarget && tex->GetConfig().levels == 1; + return tex->GetConfig().IsRenderTarget() && tex->GetConfig().levels == 1; }; if ((color_attachment && !CheckAttachment(color_attachment)) || (depth_attachment && !CheckAttachment(depth_attachment))) diff --git a/Source/Core/VideoCommon/AbstractFramebuffer.h b/Source/Core/VideoCommon/AbstractFramebuffer.h index 9d4b2d29cd..33b243e3b3 100644 --- a/Source/Core/VideoCommon/AbstractFramebuffer.h +++ b/Source/Core/VideoCommon/AbstractFramebuffer.h @@ -18,13 +18,16 @@ class AbstractTexture; class AbstractFramebuffer { public: - AbstractFramebuffer(AbstractTextureFormat color_format, AbstractTextureFormat depth_format, + AbstractFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment, + AbstractTextureFormat color_format, AbstractTextureFormat depth_format, u32 width, u32 height, u32 layers, u32 samples); virtual ~AbstractFramebuffer(); static bool ValidateConfig(const AbstractTexture* color_attachment, const AbstractTexture* depth_attachment); + AbstractTexture* GetColorAttachment() const { return m_color_attachment; } + AbstractTexture* GetDepthAttachment() const { return m_depth_attachment; } AbstractTextureFormat GetColorFormat() const { return m_color_format; } AbstractTextureFormat GetDepthFormat() const { return m_depth_format; } bool HasColorBuffer() const { return m_color_format != AbstractTextureFormat::Undefined; } @@ -36,6 +39,8 @@ public: MathUtil::Rectangle<int> GetRect() const; protected: + AbstractTexture* m_color_attachment; + AbstractTexture* m_depth_attachment; AbstractTextureFormat m_color_format; AbstractTextureFormat m_depth_format; u32 m_width; diff --git a/Source/Core/VideoCommon/AbstractPipeline.h b/Source/Core/VideoCommon/AbstractPipeline.h index c0ae61af28..8c7d7482de 100644 --- a/Source/Core/VideoCommon/AbstractPipeline.h +++ b/Source/Core/VideoCommon/AbstractPipeline.h @@ -45,24 +45,7 @@ struct AbstractPipelineConfig RasterizationState rasterization_state; DepthState depth_state; BlendingState blending_state; - - union FramebufferState - { - BitField<0, 8, AbstractTextureFormat> color_texture_format; - BitField<8, 8, AbstractTextureFormat> depth_texture_format; - BitField<16, 8, u32> samples; - BitField<24, 1, u32> per_sample_shading; - - bool operator==(const FramebufferState& rhs) const { return hex == rhs.hex; } - bool operator!=(const FramebufferState& rhs) const { return hex != rhs.hex; } - FramebufferState& operator=(const FramebufferState& rhs) - { - hex = rhs.hex; - return *this; - } - - u32 hex; - } framebuffer_state; + FramebufferState framebuffer_state; AbstractPipelineUsage usage; diff --git a/Source/Core/VideoCommon/AbstractStagingTexture.h b/Source/Core/VideoCommon/AbstractStagingTexture.h index c87dfd70b0..759f1e79e8 100644 --- a/Source/Core/VideoCommon/AbstractStagingTexture.h +++ b/Source/Core/VideoCommon/AbstractStagingTexture.h @@ -20,8 +20,16 @@ public: virtual ~AbstractStagingTexture(); const TextureConfig& GetConfig() const { return m_config; } + u32 GetWidth() const { return m_config.width; } + u32 GetHeight() const { return m_config.height; } + u32 GetLevels() const { return m_config.levels; } + u32 GetLayers() const { return m_config.layers; } + u32 GetSamples() const { return m_config.samples; } + AbstractTextureFormat GetFormat() const { return m_config.format; } + MathUtil::Rectangle<int> GetRect() const { return m_config.GetRect(); } StagingTextureType GetType() const { return m_type; } size_t GetTexelSize() const { return m_texel_size; } + bool IsMapped() const { return m_map_pointer != nullptr; } char* GetMappedPointer() const { return m_map_pointer; } size_t GetMappedStride() const { return m_map_stride; } diff --git a/Source/Core/VideoCommon/AbstractTexture.cpp b/Source/Core/VideoCommon/AbstractTexture.cpp index 6190f0e51b..d0c27617db 100644 --- a/Source/Core/VideoCommon/AbstractTexture.cpp +++ b/Source/Core/VideoCommon/AbstractTexture.cpp @@ -15,6 +15,10 @@ AbstractTexture::AbstractTexture(const TextureConfig& c) : m_config(c) { } +void AbstractTexture::FinishedRendering() +{ +} + bool AbstractTexture::Save(const std::string& filename, unsigned int level) { // We can't dump compressed textures currently (it would mean drawing them to a RGBA8 @@ -30,7 +34,7 @@ bool AbstractTexture::Save(const std::string& filename, unsigned int level) // Use a temporary staging texture for the download. Certainly not optimal, // but this is not a frequently-executed code path.. TextureConfig readback_texture_config(level_width, level_height, 1, 1, 1, - AbstractTextureFormat::RGBA8, false); + AbstractTextureFormat::RGBA8, 0); auto readback_texture = g_renderer->CreateStagingTexture(StagingTextureType::Readback, readback_texture_config); if (!readback_texture) @@ -84,7 +88,24 @@ bool AbstractTexture::IsStencilFormat(AbstractTextureFormat format) return format == AbstractTextureFormat::D24_S8 || format == AbstractTextureFormat::D32F_S8; } -size_t AbstractTexture::CalculateStrideForFormat(AbstractTextureFormat format, u32 row_length) +AbstractTextureFormat AbstractTexture::GetColorFormatForDepthFormat(AbstractTextureFormat format) +{ + switch (format) + { + case AbstractTextureFormat::D16: + return AbstractTextureFormat::R16; + + case AbstractTextureFormat::D24_S8: // TODO: Incorrect + case AbstractTextureFormat::D32F: + case AbstractTextureFormat::D32F_S8: + return AbstractTextureFormat::R32F; + + default: + return format; + } +} + +u32 AbstractTexture::CalculateStrideForFormat(AbstractTextureFormat format, u32 row_length) { switch (format) { @@ -111,7 +132,7 @@ size_t AbstractTexture::CalculateStrideForFormat(AbstractTextureFormat format, u } } -size_t AbstractTexture::GetTexelSizeForFormat(AbstractTextureFormat format) +u32 AbstractTexture::GetTexelSizeForFormat(AbstractTextureFormat format) { switch (format) { @@ -138,6 +159,21 @@ size_t AbstractTexture::GetTexelSizeForFormat(AbstractTextureFormat format) } } +u32 AbstractTexture::GetBlockSizeForFormat(AbstractTextureFormat format) +{ + switch (format) + { + case AbstractTextureFormat::DXT1: + case AbstractTextureFormat::DXT3: + case AbstractTextureFormat::DXT5: + case AbstractTextureFormat::BPTC: + return 4; + + default: + return 1; + } +} + const TextureConfig& AbstractTexture::GetConfig() const { return m_config; diff --git a/Source/Core/VideoCommon/AbstractTexture.h b/Source/Core/VideoCommon/AbstractTexture.h index b193e5459e..6ff80e9703 100644 --- a/Source/Core/VideoCommon/AbstractTexture.h +++ b/Source/Core/VideoCommon/AbstractTexture.h @@ -21,28 +21,33 @@ public: const MathUtil::Rectangle<int>& src_rect, u32 src_layer, u32 src_level, const MathUtil::Rectangle<int>& dst_rect, u32 dst_layer, u32 dst_level) = 0; - virtual void ScaleRectangleFromTexture(const AbstractTexture* source, - const MathUtil::Rectangle<int>& srcrect, - const MathUtil::Rectangle<int>& dstrect) = 0; virtual void ResolveFromTexture(const AbstractTexture* src, const MathUtil::Rectangle<int>& rect, u32 layer, u32 level) = 0; virtual void Load(u32 level, u32 width, u32 height, u32 row_length, const u8* buffer, size_t buffer_size) = 0; + // Hints to the backend that we have finished rendering to this texture, and it will be used + // as a shader resource and sampled. For Vulkan, this transitions the image layout. + virtual void FinishedRendering(); + u32 GetWidth() const { return m_config.width; } u32 GetHeight() const { return m_config.height; } u32 GetLevels() const { return m_config.levels; } u32 GetLayers() const { return m_config.layers; } u32 GetSamples() const { return m_config.samples; } AbstractTextureFormat GetFormat() const { return m_config.format; } + MathUtil::Rectangle<int> GetRect() const { return m_config.GetRect(); } + MathUtil::Rectangle<int> GetMipRect(u32 level) const { return m_config.GetMipRect(level); } bool IsMultisampled() const { return m_config.IsMultisampled(); } bool Save(const std::string& filename, unsigned int level); static bool IsCompressedFormat(AbstractTextureFormat format); static bool IsDepthFormat(AbstractTextureFormat format); static bool IsStencilFormat(AbstractTextureFormat format); - static size_t CalculateStrideForFormat(AbstractTextureFormat format, u32 row_length); - static size_t GetTexelSizeForFormat(AbstractTextureFormat format); + static AbstractTextureFormat GetColorFormatForDepthFormat(AbstractTextureFormat format); + static u32 CalculateStrideForFormat(AbstractTextureFormat format, u32 row_length); + static u32 GetTexelSizeForFormat(AbstractTextureFormat format); + static u32 GetBlockSizeForFormat(AbstractTextureFormat format); const TextureConfig& GetConfig() const; diff --git a/Source/Core/VideoCommon/BPFunctions.cpp b/Source/Core/VideoCommon/BPFunctions.cpp index 61e4c54b27..7db9d58cad 100644 --- a/Source/Core/VideoCommon/BPFunctions.cpp +++ b/Source/Core/VideoCommon/BPFunctions.cpp @@ -5,8 +5,10 @@ #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" +#include "VideoCommon/AbstractFramebuffer.h" #include "VideoCommon/BPFunctions.h" #include "VideoCommon/BPMemory.h" +#include "VideoCommon/FramebufferManager.h" #include "VideoCommon/RenderBase.h" #include "VideoCommon/RenderState.h" #include "VideoCommon/VertexManagerBase.h" @@ -51,8 +53,10 @@ void SetScissor() bpmem.scissorBR.x - xoff + 1, bpmem.scissorBR.y - yoff + 1); native_rc.ClampUL(0, 0, EFB_WIDTH, EFB_HEIGHT); - TargetRectangle target_rc = g_renderer->ConvertEFBRectangle(native_rc); - g_renderer->SetScissorRect(target_rc); + auto target_rc = g_renderer->ConvertEFBRectangle(native_rc); + auto converted_rc = + g_renderer->ConvertFramebufferRectangle(target_rc, g_renderer->GetCurrentFramebuffer()); + g_renderer->SetScissorRect(converted_rc); } void SetViewport() @@ -122,6 +126,21 @@ void SetViewport() far_depth = 1.0f - min_depth; } + // Clamp to size if oversized not supported. Required for D3D. + if (!g_ActiveConfig.backend_info.bSupportsOversizedViewports) + { + const float max_width = static_cast<float>(g_renderer->GetCurrentFramebuffer()->GetWidth()); + const float max_height = static_cast<float>(g_renderer->GetCurrentFramebuffer()->GetHeight()); + x = MathUtil::Clamp(x, 0.0f, max_width - 1.0f); + y = MathUtil::Clamp(y, 0.0f, max_height - 1.0f); + width = MathUtil::Clamp(width, 1.0f, max_width - x); + height = MathUtil::Clamp(height, 1.0f, max_height - y); + } + + // Lower-left flip. + if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin) + y = static_cast<float>(g_renderer->GetCurrentFramebuffer()->GetHeight()) - y - height; + g_renderer->SetViewport(x, y, width, height, near_depth, far_depth); } @@ -188,8 +207,6 @@ void ClearScreen(const EFBRectangle& rc) void OnPixelFormatChange() { - int convtype = -1; - // TODO : Check for Z compression format change // When using 16bit Z, the game may enable a special compression format which we need to handle // If we don't, Z values will be completely screwed up, currently only Star Wars:RS2 uses that. @@ -205,58 +222,74 @@ void OnPixelFormatChange() auto old_format = g_renderer->GetPrevPixelFormat(); auto new_format = bpmem.zcontrol.pixel_format; + g_renderer->StorePixelFormat(new_format); + + DEBUG_LOG(VIDEO, "pixelfmt: pixel=%d, zc=%d", static_cast<int>(new_format), + static_cast<int>(bpmem.zcontrol.zformat)); // no need to reinterpret pixel data in these cases if (new_format == old_format || old_format == PEControl::INVALID_FMT) - goto skip; + return; // Check for pixel format changes switch (old_format) { case PEControl::RGB8_Z24: case PEControl::Z24: + { // Z24 and RGB8_Z24 are treated equal, so just return in this case if (new_format == PEControl::RGB8_Z24 || new_format == PEControl::Z24) - goto skip; + return; if (new_format == PEControl::RGBA6_Z24) - convtype = 0; + { + g_renderer->ReinterpretPixelData(EFBReinterpretType::RGB8ToRGBA6); + return; + } else if (new_format == PEControl::RGB565_Z16) - convtype = 1; - break; + { + g_renderer->ReinterpretPixelData(EFBReinterpretType::RGB8ToRGB565); + return; + } + } + break; case PEControl::RGBA6_Z24: + { if (new_format == PEControl::RGB8_Z24 || new_format == PEControl::Z24) - convtype = 2; + { + g_renderer->ReinterpretPixelData(EFBReinterpretType::RGBA6ToRGB8); + return; + } else if (new_format == PEControl::RGB565_Z16) - convtype = 3; - break; + { + g_renderer->ReinterpretPixelData(EFBReinterpretType::RGBA6ToRGB565); + return; + } + } + break; case PEControl::RGB565_Z16: + { if (new_format == PEControl::RGB8_Z24 || new_format == PEControl::Z24) - convtype = 4; + { + g_renderer->ReinterpretPixelData(EFBReinterpretType::RGB565ToRGB8); + return; + } else if (new_format == PEControl::RGBA6_Z24) - convtype = 5; - break; + { + g_renderer->ReinterpretPixelData(EFBReinterpretType::RGB565ToRGBA6); + return; + } + } + break; default: break; } - if (convtype == -1) - { - ERROR_LOG(VIDEO, "Unhandled EFB format change: %d to %d", static_cast<int>(old_format), - static_cast<int>(new_format)); - goto skip; - } - - g_renderer->ReinterpretPixelData(convtype); - -skip: - DEBUG_LOG(VIDEO, "pixelfmt: pixel=%d, zc=%d", static_cast<int>(new_format), - static_cast<int>(bpmem.zcontrol.zformat)); - - g_renderer->StorePixelFormat(new_format); + ERROR_LOG(VIDEO, "Unhandled EFB format change: %d to %d", static_cast<int>(old_format), + static_cast<int>(new_format)); } void SetInterlacingMode(const BPCmd& bp) @@ -286,4 +319,4 @@ void SetInterlacingMode(const BPCmd& bp) break; } } -}; +}; // namespace BPFunctions diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index b29492be50..b118080eb5 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -10,11 +10,11 @@ add_library(videocommon BPStructs.cpp CPMemory.cpp CommandProcessor.cpp - Debugger.cpp DriverDetails.cpp Fifo.cpp FPSCounter.cpp - FramebufferManagerBase.cpp + FramebufferManager.cpp + FramebufferShaderGen.cpp GeometryShaderGen.cpp GeometryShaderManager.cpp HiresTextures.cpp diff --git a/Source/Core/VideoCommon/Debugger.cpp b/Source/Core/VideoCommon/Debugger.cpp deleted file mode 100644 index 4dbaa1865a..0000000000 --- a/Source/Core/VideoCommon/Debugger.cpp +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2010 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include <string> - -#include "Common/FileUtil.h" -#include "Common/StringUtil.h" -#include "Common/Thread.h" - -#include "VideoCommon/BPMemory.h" -#include "VideoCommon/Debugger.h" -#include "VideoCommon/VideoConfig.h" - -GFXDebuggerBase* g_pdebugger = nullptr; -volatile bool GFXDebuggerPauseFlag = - false; // if true, the GFX thread will be spin locked until it's false again -volatile PauseEvent GFXDebuggerToPauseAtNext = - NOT_PAUSE; // Event which will trigger spin locking the GFX thread -volatile int GFXDebuggerEventToPauseCount = - 0; // Number of events to wait for until GFX thread will be paused - -void GFXDebuggerUpdateScreen() -{ - // TODO: Implement this in a backend-independent way - /* // update screen - if (D3D::bFrameInProgress) - { - D3D::dev->SetRenderTarget(0, D3D::GetBackBufferSurface()); - D3D::dev->SetDepthStencilSurface(nullptr); - - D3D::dev->StretchRect(FramebufferManager::GetEFBColorRTSurface(), nullptr, - D3D::GetBackBufferSurface(), nullptr, - D3DTEXF_LINEAR); - - D3D::dev->EndScene(); - D3D::dev->Present(nullptr, nullptr, nullptr, nullptr); - - D3D::dev->SetRenderTarget(0, FramebufferManager::GetEFBColorRTSurface()); - D3D::dev->SetDepthStencilSurface(FramebufferManager::GetEFBDepthRTSurface()); - D3D::dev->BeginScene(); - } - else - { - D3D::dev->EndScene(); - D3D::dev->Present(nullptr, nullptr, nullptr, nullptr); - D3D::dev->BeginScene(); - }*/ -} - -// GFX thread -void GFXDebuggerCheckAndPause(bool update) -{ - if (GFXDebuggerPauseFlag) - { - g_pdebugger->OnPause(); - while (GFXDebuggerPauseFlag) - { - if (update) - GFXDebuggerUpdateScreen(); - Common::SleepCurrentThread(5); - } - g_pdebugger->OnContinue(); - } -} - -// GFX thread -void GFXDebuggerToPause(bool update) -{ - GFXDebuggerToPauseAtNext = NOT_PAUSE; - GFXDebuggerPauseFlag = true; - GFXDebuggerCheckAndPause(update); -} - -void ContinueGFXDebugger() -{ - GFXDebuggerPauseFlag = false; -} - -void GFXDebuggerBase::DumpPixelShader(const std::string& path) -{ - const std::string filename = StringFromFormat("%sdump_ps.txt", path.c_str()); - - std::string output; - bool useDstAlpha = bpmem.dstalpha.enable && bpmem.blendmode.alphaupdate && - bpmem.zcontrol.pixel_format == PEControl::RGBA6_Z24; - if (!useDstAlpha) - { - output = "Destination alpha disabled:\n"; - /// output += GeneratePixelShaderCode(DSTALPHA_NONE, g_ActiveConfig.backend_info.APIType, - /// g_nativeVertexFmt->m_components); - } - else - { - if (g_ActiveConfig.backend_info.bSupportsDualSourceBlend) - { - output = "Using dual source blending for destination alpha:\n"; - /// output += GeneratePixelShaderCode(DSTALPHA_DUAL_SOURCE_BLEND, - /// g_ActiveConfig.backend_info.APIType, g_nativeVertexFmt->m_components); - } - else - { - output = "Using two passes for emulating destination alpha:\n"; - /// output += GeneratePixelShaderCode(DSTALPHA_NONE, g_ActiveConfig.backend_info.APIType, - /// g_nativeVertexFmt->m_components); - output += "\n\nDestination alpha pass shader:\n"; - /// output += GeneratePixelShaderCode(DSTALPHA_ALPHA_PASS, - /// g_ActiveConfig.backend_info.APIType, g_nativeVertexFmt->m_components); - } - } - - File::CreateEmptyFile(filename); - File::WriteStringToFile(output, filename); -} - -void GFXDebuggerBase::DumpVertexShader(const std::string& path) -{ - const std::string filename = StringFromFormat("%sdump_vs.txt", path.c_str()); - - File::CreateEmptyFile(filename); - /// File::WriteStringToFile(GenerateVertexShaderCode(g_nativeVertexFmt->m_components, - /// g_ActiveConfig.backend_info.APIType), filename); -} - -void GFXDebuggerBase::DumpPixelShaderConstants(const std::string& path) -{ - // TODO -} - -void GFXDebuggerBase::DumpVertexShaderConstants(const std::string& path) -{ - // TODO -} - -void GFXDebuggerBase::DumpTextures(const std::string& path) -{ - // TODO -} - -void GFXDebuggerBase::DumpFrameBuffer(const std::string& path) -{ - // TODO -} - -void GFXDebuggerBase::DumpGeometry(const std::string& path) -{ - // TODO -} - -void GFXDebuggerBase::DumpVertexDecl(const std::string& path) -{ - // TODO -} - -void GFXDebuggerBase::DumpMatrices(const std::string& path) -{ - // TODO -} - -void GFXDebuggerBase::DumpStats(const std::string& path) -{ - // TODO -} diff --git a/Source/Core/VideoCommon/Debugger.h b/Source/Core/VideoCommon/Debugger.h deleted file mode 100644 index 1c2d293edc..0000000000 --- a/Source/Core/VideoCommon/Debugger.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2010 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include <string> - -class GFXDebuggerBase -{ -public: - virtual ~GFXDebuggerBase() {} - // if paused, debugging functions can be enabled - virtual void OnPause() {} - virtual void OnContinue() {} - void DumpPixelShader(const std::string& path); - void DumpVertexShader(const std::string& path); - void DumpPixelShaderConstants(const std::string& path); - void DumpVertexShaderConstants(const std::string& path); - void DumpTextures(const std::string& path); - void DumpFrameBuffer(const std::string& path); - void DumpGeometry(const std::string& path); - void DumpVertexDecl(const std::string& path); - void DumpMatrices(const std::string& path); - void DumpStats(const std::string& path); -}; - -enum PauseEvent -{ - NOT_PAUSE = 0, - NEXT_FRAME = 1 << 0, - NEXT_FLUSH = 1 << 1, - - NEXT_PIXEL_SHADER_CHANGE = 1 << 2, - NEXT_VERTEX_SHADER_CHANGE = 1 << 3, - NEXT_TEXTURE_CHANGE = 1 << 4, - NEXT_NEW_TEXTURE = 1 << 5, - - NEXT_XFB_CMD = 1 << 6, // TODO - NEXT_EFB_CMD = 1 << 7, // TODO - - NEXT_MATRIX_CMD = 1 << 8, // TODO - NEXT_VERTEX_CMD = 1 << 9, // TODO - NEXT_TEXTURE_CMD = 1 << 10, // TODO - NEXT_LIGHT_CMD = 1 << 11, // TODO - NEXT_FOG_CMD = 1 << 12, // TODO - - NEXT_SET_TLUT = 1 << 13, // TODO - - NEXT_ERROR = 1 << 14, // TODO -}; - -extern GFXDebuggerBase* g_pdebugger; -extern volatile bool GFXDebuggerPauseFlag; -extern volatile PauseEvent GFXDebuggerToPauseAtNext; -extern volatile int GFXDebuggerEventToPauseCount; -void ContinueGFXDebugger(); -void GFXDebuggerCheckAndPause(bool update); -void GFXDebuggerToPause(bool update); -void GFXDebuggerUpdateScreen(); - -#define GFX_DEBUGGER_PAUSE_AT(event, update) \ - { \ - if (((GFXDebuggerToPauseAtNext & event) && --GFXDebuggerEventToPauseCount <= 0) || \ - GFXDebuggerPauseFlag) \ - GFXDebuggerToPause(update); \ - } -#define GFX_DEBUGGER_PAUSE_LOG_AT(event, update, dumpfunc) \ - { \ - if (((GFXDebuggerToPauseAtNext & event) && --GFXDebuggerEventToPauseCount <= 0) || \ - GFXDebuggerPauseFlag) \ - { \ - {dumpfunc}; \ - GFXDebuggerToPause(update); \ - } \ - } -#define GFX_DEBUGGER_LOG_AT(event, dumpfunc) \ - { \ - if ((GFXDebuggerToPauseAtNext & event)) \ - { \ - {dumpfunc}; \ - } \ - } diff --git a/Source/Core/VideoCommon/FramebufferManager.cpp b/Source/Core/VideoCommon/FramebufferManager.cpp new file mode 100644 index 0000000000..0af19012ea --- /dev/null +++ b/Source/Core/VideoCommon/FramebufferManager.cpp @@ -0,0 +1,764 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoCommon/FramebufferManager.h" +#include <memory> +#include "VideoCommon/FramebufferShaderGen.h" +#include "VideoCommon/VertexManagerBase.h" + +#include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" +#include "VideoCommon/AbstractFramebuffer.h" +#include "VideoCommon/AbstractPipeline.h" +#include "VideoCommon/AbstractShader.h" +#include "VideoCommon/AbstractStagingTexture.h" +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/DriverDetails.h" +#include "VideoCommon/RenderBase.h" +#include "VideoCommon/VideoConfig.h" + +// Maximum number of pixels poked in one batch * 6 +constexpr size_t MAX_POKE_VERTICES = 32768; + +std::unique_ptr<FramebufferManager> g_framebuffer_manager; + +FramebufferManager::FramebufferManager() = default; + +FramebufferManager::~FramebufferManager() +{ + DestroyClearPipelines(); + DestroyPokePipelines(); + DestroyConversionPipelines(); + DestroyReadbackPipelines(); + DestroyReadbackFramebuffer(); + DestroyEFBFramebuffer(); +} + +bool FramebufferManager::Initialize() +{ + if (!CreateEFBFramebuffer()) + { + PanicAlert("Failed to create EFB framebuffer"); + return false; + } + + if (!CreateReadbackFramebuffer()) + { + PanicAlert("Failed to create EFB readback framebuffer"); + return false; + } + + if (!CompileReadbackPipelines()) + { + PanicAlert("Failed to compile EFB readback pipelines"); + return false; + } + + if (!CompileConversionPipelines()) + { + PanicAlert("Failed to compile EFB conversion pipelines"); + return false; + } + + if (!CompileClearPipelines()) + { + PanicAlert("Failed to compile EFB clear pipelines"); + return false; + } + + if (!CompilePokePipelines()) + { + PanicAlert("Failed to compile EFB poke pipelines"); + return false; + } + + return true; +} + +void FramebufferManager::RecreateEFBFramebuffer() +{ + FlushEFBPokes(); + InvalidatePeekCache(); + + DestroyReadbackFramebuffer(); + DestroyEFBFramebuffer(); + if (!CreateEFBFramebuffer() || !CreateReadbackFramebuffer()) + PanicAlert("Failed to recreate EFB framebuffer"); +} + +void FramebufferManager::RecompileShaders() +{ + DestroyPokePipelines(); + DestroyClearPipelines(); + DestroyConversionPipelines(); + DestroyReadbackPipelines(); + if (!CompileReadbackPipelines() || !CompileConversionPipelines() || !CompileClearPipelines() || + !CompilePokePipelines()) + { + PanicAlert("Failed to recompile EFB pipelines"); + } +} + +AbstractTextureFormat FramebufferManager::GetEFBColorFormat() +{ + // The EFB can be set to different pixel formats by the game through the + // BPMEM_ZCOMPARE register (which should probably have a different name). + // They are: + // - 24-bit RGB (8-bit components) with 24-bit Z + // - 24-bit RGBA (6-bit components) with 24-bit Z + // - Multisampled 16-bit RGB (5-6-5 format) with 16-bit Z + // We only use one EFB format here: 32-bit ARGB with 32-bit Z. + // Multisampling depends on user settings. + // The distinction becomes important for certain operations, i.e. the + // alpha channel should be ignored if the EFB does not have one. + return AbstractTextureFormat::RGBA8; +} + +AbstractTextureFormat FramebufferManager::GetEFBDepthFormat() +{ + // 32-bit depth clears are broken in the Adreno Vulkan driver, and have no effect. + // To work around this, we use a D24_S8 buffer instead, which results in a loss of accuracy. + // We still resolve this to a R32F texture, as there is no 24-bit format. + if (DriverDetails::HasBug(DriverDetails::BUG_BROKEN_D32F_CLEAR)) + return AbstractTextureFormat::D24_S8; + else + return AbstractTextureFormat::D32F; +} + +static u32 CalculateEFBLayers() +{ + return (g_ActiveConfig.stereo_mode != StereoMode::Off) ? 2 : 1; +} + +TextureConfig FramebufferManager::GetEFBColorTextureConfig() +{ + return TextureConfig(g_renderer->GetTargetWidth(), g_renderer->GetTargetHeight(), 1, + CalculateEFBLayers(), g_ActiveConfig.iMultisamples, GetEFBColorFormat(), + AbstractTextureFlag_RenderTarget); +} + +TextureConfig FramebufferManager::GetEFBDepthTextureConfig() +{ + return TextureConfig(g_renderer->GetTargetWidth(), g_renderer->GetTargetHeight(), 1, + CalculateEFBLayers(), g_ActiveConfig.iMultisamples, GetEFBDepthFormat(), + AbstractTextureFlag_RenderTarget); +} + +FramebufferState FramebufferManager::GetEFBFramebufferState() const +{ + FramebufferState ret = {}; + ret.color_texture_format = m_efb_color_texture->GetFormat(); + ret.depth_texture_format = m_efb_depth_texture->GetFormat(); + ret.per_sample_shading = IsEFBMultisampled() && g_ActiveConfig.bSSAA; + ret.samples = m_efb_color_texture->GetSamples(); + return ret; +} + +bool FramebufferManager::CreateEFBFramebuffer() +{ + const TextureConfig efb_color_texture_config = GetEFBColorTextureConfig(); + const TextureConfig efb_depth_texture_config = GetEFBDepthTextureConfig(); + + // We need a second texture to swap with for changing pixel formats + m_efb_color_texture = g_renderer->CreateTexture(efb_color_texture_config); + m_efb_depth_texture = g_renderer->CreateTexture(efb_depth_texture_config); + m_efb_convert_color_texture = g_renderer->CreateTexture(efb_color_texture_config); + if (!m_efb_color_texture || !m_efb_depth_texture || !m_efb_convert_color_texture) + return false; + + m_efb_framebuffer = + g_renderer->CreateFramebuffer(m_efb_color_texture.get(), m_efb_depth_texture.get()); + m_efb_convert_framebuffer = + g_renderer->CreateFramebuffer(m_efb_convert_color_texture.get(), m_efb_depth_texture.get()); + if (!m_efb_framebuffer || !m_efb_convert_framebuffer) + return false; + + // Create resolved textures if MSAA is on + if (g_ActiveConfig.MultisamplingEnabled()) + { + m_efb_resolve_color_texture = g_renderer->CreateTexture( + TextureConfig(efb_color_texture_config.width, efb_color_texture_config.height, 1, + efb_color_texture_config.layers, 1, efb_color_texture_config.format, 0)); + m_efb_depth_resolve_texture = g_renderer->CreateTexture(TextureConfig( + efb_depth_texture_config.width, efb_depth_texture_config.height, 1, + efb_depth_texture_config.layers, 1, + AbstractTexture::GetColorFormatForDepthFormat(efb_depth_texture_config.format), + AbstractTextureFlag_RenderTarget)); + if (!m_efb_resolve_color_texture || !m_efb_depth_resolve_texture) + return false; + + m_efb_depth_resolve_framebuffer = + g_renderer->CreateFramebuffer(m_efb_depth_resolve_texture.get(), nullptr); + if (!m_efb_depth_resolve_framebuffer) + return false; + } + + // Clear the renderable textures out. + g_renderer->SetAndClearFramebuffer( + m_efb_framebuffer.get(), {{0.0f, 0.0f, 0.0f, 0.0f}}, + g_ActiveConfig.backend_info.bSupportsReversedDepthRange ? 1.0f : 0.0f); + return true; +} + +void FramebufferManager::DestroyEFBFramebuffer() +{ + m_efb_framebuffer.reset(); + m_efb_convert_framebuffer.reset(); + m_efb_color_texture.reset(); + m_efb_convert_color_texture.reset(); + m_efb_depth_texture.reset(); + m_efb_resolve_color_texture.reset(); + m_efb_depth_resolve_framebuffer.reset(); + m_efb_depth_resolve_texture.reset(); +} + +void FramebufferManager::BindEFBFramebuffer() +{ + g_renderer->SetFramebuffer(m_efb_framebuffer.get()); +} + +AbstractTexture* FramebufferManager::ResolveEFBColorTexture(const MathUtil::Rectangle<int>& region) +{ + // Return the normal EFB texture if multisampling is off. + if (!IsEFBMultisampled()) + { + m_efb_color_texture->FinishedRendering(); + return m_efb_color_texture.get(); + } + + // It's not valid to resolve an out-of-range rectangle. + MathUtil::Rectangle<int> clamped_region = region; + clamped_region.ClampUL(0, 0, GetEFBWidth(), GetEFBHeight()); + clamped_region = g_renderer->ConvertFramebufferRectangle(clamped_region, m_efb_framebuffer.get()); + + // Resolve to our already-created texture. + for (u32 layer = 0; layer < GetEFBLayers(); layer++) + { + m_efb_resolve_color_texture->ResolveFromTexture(m_efb_color_texture.get(), clamped_region, + layer, 0); + } + + m_efb_resolve_color_texture->FinishedRendering(); + return m_efb_resolve_color_texture.get(); +} + +AbstractTexture* FramebufferManager::ResolveEFBDepthTexture(const MathUtil::Rectangle<int>& region) +{ + if (!IsEFBMultisampled()) + { + m_efb_depth_texture->FinishedRendering(); + return m_efb_depth_texture.get(); + } + + // It's not valid to resolve an out-of-range rectangle. + MathUtil::Rectangle<int> clamped_region = region; + clamped_region.ClampUL(0, 0, GetEFBWidth(), GetEFBHeight()); + clamped_region = g_renderer->ConvertFramebufferRectangle(clamped_region, m_efb_framebuffer.get()); + + m_efb_depth_texture->FinishedRendering(); + g_renderer->BeginUtilityDrawing(); + g_renderer->SetAndDiscardFramebuffer(m_efb_depth_resolve_framebuffer.get()); + g_renderer->SetPipeline(m_efb_depth_resolve_pipeline.get()); + g_renderer->SetTexture(0, m_efb_depth_texture.get()); + g_renderer->SetSamplerState(0, RenderState::GetPointSamplerState()); + g_renderer->SetViewportAndScissor(clamped_region); + g_renderer->Draw(0, 3); + m_efb_depth_resolve_texture->FinishedRendering(); + g_renderer->EndUtilityDrawing(); + + return m_efb_depth_resolve_texture.get(); +} + +bool FramebufferManager::ReinterpretPixelData(EFBReinterpretType convtype) +{ + if (!m_format_conversion_pipelines[static_cast<u32>(convtype)]) + return false; + + // Draw to the secondary framebuffer. + m_efb_color_texture->FinishedRendering(); + g_renderer->BeginUtilityDrawing(); + g_renderer->SetAndDiscardFramebuffer(m_efb_convert_framebuffer.get()); + g_renderer->SetViewportAndScissor(m_efb_framebuffer->GetRect()); + g_renderer->SetPipeline(m_format_conversion_pipelines[static_cast<u32>(convtype)].get()); + g_renderer->SetTexture(0, m_efb_color_texture.get()); + g_renderer->Draw(0, 3); + + // And swap the framebuffers around, so we do new drawing to the converted framebuffer. + std::swap(m_efb_color_texture, m_efb_convert_color_texture); + std::swap(m_efb_framebuffer, m_efb_convert_framebuffer); + g_renderer->EndUtilityDrawing(); + return true; +} + +bool FramebufferManager::CompileConversionPipelines() +{ + for (u32 i = 0; i < NUM_EFB_REINTERPRET_TYPES; i++) + { + std::unique_ptr<AbstractShader> pixel_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Pixel, FramebufferShaderGen::GenerateFormatConversionShader( + static_cast<EFBReinterpretType>(i), GetEFBSamples())); + if (!pixel_shader) + return false; + + AbstractPipelineConfig config = {}; + config.vertex_shader = g_shader_cache->GetScreenQuadVertexShader(); + config.geometry_shader = IsEFBStereo() ? g_shader_cache->GetTexcoordGeometryShader() : nullptr; + config.pixel_shader = pixel_shader.get(); + config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles); + config.depth_state = RenderState::GetNoDepthTestingDepthState(); + config.blending_state = RenderState::GetNoBlendingBlendState(); + config.framebuffer_state = GetEFBFramebufferState(); + config.usage = AbstractPipelineUsage::Utility; + m_format_conversion_pipelines[i] = g_renderer->CreatePipeline(config); + if (!m_format_conversion_pipelines[i]) + return false; + } + + return true; +} + +void FramebufferManager::DestroyConversionPipelines() +{ + for (auto& pipeline : m_format_conversion_pipelines) + pipeline.reset(); +} + +bool FramebufferManager::PopulateColorReadbackTexture() +{ + g_vertex_manager->OnCPUEFBAccess(); + + // Issue a copy from framebuffer -> copy texture if we have >1xIR or MSAA on. + AbstractTexture* src_texture = + ResolveEFBColorTexture(MathUtil::Rectangle<int>(0, 0, GetEFBWidth(), GetEFBHeight())); + if (g_renderer->GetEFBScale() != 1) + { + // Downsample from internal resolution to 1x. + // TODO: This won't produce correct results at IRs above 2x. + g_renderer->BeginUtilityDrawing(); + g_renderer->SetAndDiscardFramebuffer(m_color_copy_framebuffer.get()); + g_renderer->SetViewportAndScissor(m_color_copy_framebuffer->GetRect()); + g_renderer->SetPipeline(m_color_copy_pipeline.get()); + g_renderer->SetTexture(0, src_texture); + g_renderer->SetSamplerState(0, RenderState::GetLinearSamplerState()); + g_renderer->Draw(0, 3); + + // Copy from EFB or copy texture to staging texture. + m_color_readback_texture->CopyFromTexture(m_color_copy_texture.get(), + m_color_readback_texture->GetRect(), 0, 0, + m_color_readback_texture->GetRect()); + + g_renderer->EndUtilityDrawing(); + } + else + { + m_color_readback_texture->CopyFromTexture(src_texture, m_color_readback_texture->GetRect(), 0, + 0, m_color_readback_texture->GetRect()); + } + + // Wait until the copy is complete. + m_color_readback_texture->Flush(); + m_color_readback_texture_valid = true; + return true; +} + +bool FramebufferManager::PopulateDepthReadbackTexture() +{ + g_vertex_manager->OnCPUEFBAccess(); + + // Issue a copy from framebuffer -> copy texture if we have >1xIR or MSAA on. + AbstractTexture* src_texture = + ResolveEFBDepthTexture(MathUtil::Rectangle<int>(0, 0, GetEFBWidth(), GetEFBHeight())); + if (g_renderer->GetEFBScale() != 1) + { + // Downsample from internal resolution to 1x. + // TODO: This won't produce correct results at IRs above 2x. + g_renderer->BeginUtilityDrawing(); + g_renderer->SetAndDiscardFramebuffer(m_depth_copy_framebuffer.get()); + g_renderer->SetViewportAndScissor(m_depth_copy_framebuffer->GetRect()); + g_renderer->SetPipeline(m_depth_copy_pipeline.get()); + g_renderer->SetTexture(0, src_texture); + g_renderer->SetSamplerState(0, RenderState::GetLinearSamplerState()); + g_renderer->Draw(0, 3); + + // No need to call FinishedRendering() here because CopyFromTexture() transitions. + m_depth_readback_texture->CopyFromTexture(m_depth_copy_texture.get(), + m_depth_readback_texture->GetRect(), 0, 0, + m_depth_readback_texture->GetRect()); + + g_renderer->EndUtilityDrawing(); + } + else + { + m_depth_readback_texture->CopyFromTexture(src_texture, m_depth_readback_texture->GetRect(), 0, + 0, m_depth_readback_texture->GetRect()); + } + + // Wait until the copy is complete. + m_depth_readback_texture->Flush(); + m_depth_readback_texture_valid = true; + return true; +} + +void FramebufferManager::InvalidatePeekCache() +{ + m_color_readback_texture_valid = false; + m_depth_readback_texture_valid = false; +} + +bool FramebufferManager::CompileReadbackPipelines() +{ + AbstractPipelineConfig config = {}; + config.vertex_shader = g_shader_cache->GetScreenQuadVertexShader(); + config.geometry_shader = IsEFBStereo() ? g_shader_cache->GetTexcoordGeometryShader() : nullptr; + config.pixel_shader = g_shader_cache->GetTextureCopyPixelShader(); + config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles); + config.depth_state = RenderState::GetNoDepthTestingDepthState(); + config.blending_state = RenderState::GetNoBlendingBlendState(); + config.framebuffer_state = RenderState::GetColorFramebufferState(GetEFBColorFormat()); + config.usage = AbstractPipelineUsage::Utility; + m_color_copy_pipeline = g_renderer->CreatePipeline(config); + if (!m_color_copy_pipeline) + return false; + + // same for depth, except different format + config.framebuffer_state.color_texture_format = + AbstractTexture::GetColorFormatForDepthFormat(GetEFBDepthFormat()); + m_depth_copy_pipeline = g_renderer->CreatePipeline(config); + if (!m_depth_copy_pipeline) + return false; + + if (IsEFBMultisampled()) + { + auto depth_resolve_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Pixel, FramebufferShaderGen::GenerateResolveDepthPixelShader(GetEFBSamples())); + if (!depth_resolve_shader) + return false; + + config.pixel_shader = depth_resolve_shader.get(); + m_efb_depth_resolve_pipeline = g_renderer->CreatePipeline(config); + if (!m_efb_depth_resolve_pipeline) + return false; + } + + return true; +} + +void FramebufferManager::DestroyReadbackPipelines() +{ + m_efb_depth_resolve_pipeline.reset(); + m_depth_copy_pipeline.reset(); + m_color_copy_pipeline.reset(); +} + +bool FramebufferManager::CreateReadbackFramebuffer() +{ + const TextureConfig color_config(EFB_WIDTH, EFB_HEIGHT, 1, 1, 1, GetEFBColorFormat(), + AbstractTextureFlag_RenderTarget); + const TextureConfig depth_config( + EFB_WIDTH, EFB_HEIGHT, 1, 1, 1, + AbstractTexture::GetColorFormatForDepthFormat(GetEFBDepthFormat()), + AbstractTextureFlag_RenderTarget); + if (g_renderer->GetEFBScale() != 1) + { + m_color_copy_texture = g_renderer->CreateTexture(color_config); + m_depth_copy_texture = g_renderer->CreateTexture(depth_config); + if (!m_color_copy_texture || !m_depth_copy_texture) + return false; + + m_color_copy_framebuffer = g_renderer->CreateFramebuffer(m_color_copy_texture.get(), nullptr); + m_depth_copy_framebuffer = g_renderer->CreateFramebuffer(m_depth_copy_texture.get(), nullptr); + if (!m_color_copy_framebuffer || !m_depth_copy_framebuffer) + return false; + } + + m_color_readback_texture = + g_renderer->CreateStagingTexture(StagingTextureType::Mutable, color_config); + m_depth_readback_texture = + g_renderer->CreateStagingTexture(StagingTextureType::Mutable, depth_config); + if (!m_color_readback_texture || !m_depth_readback_texture) + return false; + + return true; +} + +void FramebufferManager::DestroyReadbackFramebuffer() +{ + m_depth_copy_framebuffer.reset(); + m_depth_copy_texture.reset(); + m_depth_readback_texture_valid = false; + m_color_copy_framebuffer.reset(); + m_color_copy_texture.reset(); + m_color_readback_texture_valid = false; +} + +void FramebufferManager::ClearEFB(const MathUtil::Rectangle<int>& rc, bool clear_color, + bool clear_alpha, bool clear_z, u32 color, u32 z) +{ + FlushEFBPokes(); + InvalidatePeekCache(); + g_renderer->BeginUtilityDrawing(); + + // Set up uniforms. + struct Uniforms + { + float clear_color[4]; + float clear_depth; + float padding1, padding2, padding3; + }; + static_assert(std::is_standard_layout<Uniforms>::value); + Uniforms uniforms = {{static_cast<float>((color >> 16) & 0xFF) / 255.0f, + static_cast<float>((color >> 8) & 0xFF) / 255.0f, + static_cast<float>((color >> 0) & 0xFF) / 255.0f, + static_cast<float>((color >> 24) & 0xFF) / 255.0f}, + static_cast<float>(z & 0xFFFFFF) / 16777216.0f}; + if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange) + uniforms.clear_depth = 1.0f - uniforms.clear_depth; + g_vertex_manager->UploadUtilityUniforms(&uniforms, sizeof(uniforms)); + + const auto target_rc = g_renderer->ConvertFramebufferRectangle( + g_renderer->ConvertEFBRectangle(rc), m_efb_framebuffer.get()); + g_renderer->SetPipeline(m_efb_clear_pipelines[clear_color][clear_alpha][clear_z].get()); + g_renderer->SetViewportAndScissor(target_rc); + g_renderer->Draw(0, 3); + g_renderer->EndUtilityDrawing(); +} + +bool FramebufferManager::CompileClearPipelines() +{ + auto vertex_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Vertex, FramebufferShaderGen::GenerateClearVertexShader()); + if (!vertex_shader) + return false; + + AbstractPipelineConfig config; + config.vertex_format = nullptr; + config.vertex_shader = vertex_shader.get(); + config.geometry_shader = IsEFBStereo() ? g_shader_cache->GetColorGeometryShader() : nullptr; + config.pixel_shader = g_shader_cache->GetColorPixelShader(); + config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles); + config.depth_state = RenderState::GetAlwaysWriteDepthState(); + config.blending_state = RenderState::GetNoBlendingBlendState(); + config.framebuffer_state = GetEFBFramebufferState(); + config.usage = AbstractPipelineUsage::Utility; + + for (u32 color_enable = 0; color_enable < 2; color_enable++) + { + config.blending_state.colorupdate = color_enable != 0; + for (u32 alpha_enable = 0; alpha_enable < 2; alpha_enable++) + { + config.blending_state.alphaupdate = alpha_enable != 0; + for (u32 depth_enable = 0; depth_enable < 2; depth_enable++) + { + config.depth_state.testenable = depth_enable != 0; + config.depth_state.updateenable = depth_enable != 0; + + m_efb_clear_pipelines[color_enable][alpha_enable][depth_enable] = + g_renderer->CreatePipeline(config); + if (!m_efb_clear_pipelines[color_enable][alpha_enable][depth_enable]) + return false; + } + } + } + + return true; +} + +void FramebufferManager::DestroyClearPipelines() +{ + for (u32 color_enable = 0; color_enable < 2; color_enable++) + { + for (u32 alpha_enable = 0; alpha_enable < 2; alpha_enable++) + { + for (u32 depth_enable = 0; depth_enable < 2; depth_enable++) + { + m_efb_clear_pipelines[color_enable][alpha_enable][depth_enable].reset(); + } + } + } +} + +u32 FramebufferManager::PeekEFBColor(u32 x, u32 y) +{ + if (!m_color_readback_texture_valid && !PopulateColorReadbackTexture()) + return 0; + + // The y coordinate here assumes upper-left origin, but the readback texture is lower-left in GL. + if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin) + y = EFB_HEIGHT - 1 - y; + + u32 value; + m_color_readback_texture->ReadTexel(x, y, &value); + return value; +} + +float FramebufferManager::PeekEFBDepth(u32 x, u32 y) +{ + if (!m_depth_readback_texture_valid && !PopulateDepthReadbackTexture()) + return 0.0f; + + // The y coordinate here assumes upper-left origin, but the readback texture is lower-left in GL. + if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin) + y = EFB_HEIGHT - 1 - y; + + float value; + m_depth_readback_texture->ReadTexel(x, y, &value); + return value; +} + +void FramebufferManager::PokeEFBColor(u32 x, u32 y, u32 color) +{ + // Flush if we exceeded the number of vertices per batch. + if ((m_color_poke_vertices.size() + 6) > MAX_POKE_VERTICES) + FlushEFBPokes(); + + CreatePokeVertices(&m_color_poke_vertices, x, y, 0.0f, color); + + // Update the peek cache if it's valid, since we know the color of the pixel now. + if (m_color_readback_texture_valid) + { + // See comment above for reasoning for lower-left coordinates. + if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin) + y = EFB_HEIGHT - 1 - y; + + m_color_readback_texture->WriteTexel(x, y, &color); + } +} + +void FramebufferManager::PokeEFBDepth(u32 x, u32 y, float depth) +{ + // Flush if we exceeded the number of vertices per batch. + if ((m_color_poke_vertices.size() + 6) > MAX_POKE_VERTICES) + FlushEFBPokes(); + + CreatePokeVertices(&m_depth_poke_vertices, x, y, depth, 0); + + // Update the peek cache if it's valid, since we know the color of the pixel now. + if (m_depth_readback_texture_valid) + { + // See comment above for reasoning for lower-left coordinates. + if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin) + y = EFB_HEIGHT - 1 - y; + + m_depth_readback_texture->WriteTexel(x, y, &depth); + } +} + +void FramebufferManager::CreatePokeVertices(std::vector<EFBPokeVertex>* destination_list, u32 x, + u32 y, float z, u32 color) +{ + const float cs_pixel_width = 1.0f / EFB_WIDTH * 2.0f; + const float cs_pixel_height = 1.0f / EFB_HEIGHT * 2.0f; + if (g_ActiveConfig.backend_info.bSupportsLargePoints) + { + // GPU will expand the point to a quad. + const float cs_x = (static_cast<float>(x) + 0.5f) * cs_pixel_width - 1.0f; + const float cs_y = 1.0f - (static_cast<float>(y) + 0.5f) * cs_pixel_height; + const float point_size = static_cast<float>(g_renderer->GetEFBScale()); + destination_list->push_back({{cs_x, cs_y, z, point_size}, color}); + return; + } + + // Generate quad from the single point (clip-space coordinates). + const float x1 = static_cast<float>(x) * cs_pixel_width - 1.0f; + const float y1 = 1.0f - static_cast<float>(y) * cs_pixel_height; + const float x2 = x1 + cs_pixel_width; + const float y2 = y1 + cs_pixel_height; + destination_list->push_back({{x1, y1, z, 1.0f}, color}); + destination_list->push_back({{x2, y1, z, 1.0f}, color}); + destination_list->push_back({{x1, y2, z, 1.0f}, color}); + destination_list->push_back({{x1, y2, z, 1.0f}, color}); + destination_list->push_back({{x2, y1, z, 1.0f}, color}); + destination_list->push_back({{x2, y2, z, 1.0f}, color}); +} + +void FramebufferManager::FlushEFBPokes() +{ + if (!m_color_poke_vertices.empty()) + { + DrawPokeVertices(m_color_poke_vertices.data(), static_cast<u32>(m_color_poke_vertices.size()), + m_color_poke_pipeline.get()); + m_color_poke_vertices.clear(); + } + + if (!m_depth_poke_vertices.empty()) + { + DrawPokeVertices(m_depth_poke_vertices.data(), static_cast<u32>(m_depth_poke_vertices.size()), + m_depth_poke_pipeline.get()); + m_depth_poke_vertices.clear(); + } +} + +void FramebufferManager::DrawPokeVertices(const EFBPokeVertex* vertices, u32 vertex_count, + const AbstractPipeline* pipeline) +{ + // Copy to vertex buffer. + g_renderer->BeginUtilityDrawing(); + u32 base_vertex, base_index; + g_vertex_manager->UploadUtilityVertices(vertices, sizeof(EFBPokeVertex), + static_cast<u32>(vertex_count), nullptr, 0, &base_vertex, + &base_index); + + // Now we can draw. + g_renderer->SetViewportAndScissor(m_efb_framebuffer->GetRect()); + g_renderer->SetPipeline(pipeline); + g_renderer->Draw(base_vertex, vertex_count); + g_renderer->EndUtilityDrawing(); +} + +bool FramebufferManager::CompilePokePipelines() +{ + PortableVertexDeclaration vtx_decl = {}; + vtx_decl.position.enable = true; + vtx_decl.position.type = VAR_FLOAT; + vtx_decl.position.components = 4; + vtx_decl.position.integer = false; + vtx_decl.position.offset = offsetof(EFBPokeVertex, position); + vtx_decl.colors[0].enable = true; + vtx_decl.colors[0].type = VAR_UNSIGNED_BYTE; + vtx_decl.colors[0].components = 4; + vtx_decl.colors[0].integer = false; + vtx_decl.colors[0].offset = offsetof(EFBPokeVertex, color); + vtx_decl.stride = sizeof(EFBPokeVertex); + + m_poke_vertex_format = g_renderer->CreateNativeVertexFormat(vtx_decl); + if (!m_poke_vertex_format) + return false; + + auto poke_vertex_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Vertex, FramebufferShaderGen::GenerateEFBPokeVertexShader()); + if (!poke_vertex_shader) + return false; + + AbstractPipelineConfig config = {}; + config.vertex_format = m_poke_vertex_format.get(); + config.vertex_shader = poke_vertex_shader.get(); + config.geometry_shader = IsEFBStereo() ? g_shader_cache->GetColorGeometryShader() : nullptr; + config.pixel_shader = g_shader_cache->GetColorPixelShader(); + config.rasterization_state = RenderState::GetNoCullRasterizationState( + g_ActiveConfig.backend_info.bSupportsLargePoints ? PrimitiveType::Points : + PrimitiveType::Triangles); + config.depth_state = RenderState::GetNoDepthTestingDepthState(); + config.blending_state = RenderState::GetNoBlendingBlendState(); + config.framebuffer_state = GetEFBFramebufferState(); + config.usage = AbstractPipelineUsage::Utility; + m_color_poke_pipeline = g_renderer->CreatePipeline(config); + if (!m_color_poke_pipeline) + return false; + + // Turn off color writes, depth writes on for depth pokes. + config.depth_state = RenderState::GetAlwaysWriteDepthState(); + config.blending_state = RenderState::GetNoColorWriteBlendState(); + m_depth_poke_pipeline = g_renderer->CreatePipeline(config); + if (!m_depth_poke_pipeline) + return false; + + return true; +} + +void FramebufferManager::DestroyPokePipelines() +{ + m_depth_poke_pipeline.reset(); + m_color_poke_pipeline.reset(); + m_poke_vertex_format.reset(); +} diff --git a/Source/Core/VideoCommon/FramebufferManager.h b/Source/Core/VideoCommon/FramebufferManager.h new file mode 100644 index 0000000000..d0de7b62b1 --- /dev/null +++ b/Source/Core/VideoCommon/FramebufferManager.h @@ -0,0 +1,171 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <memory> + +#include "Common/CommonTypes.h" +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/RenderState.h" +#include "VideoCommon/TextureConfig.h" + +class AbstractFramebuffer; +class AbstractPipeline; +class AbstractStagingTexture; +class NativeVertexFormat; + +enum class EFBReinterpretType +{ + RGB8ToRGB565 = 0, + RGB8ToRGBA6 = 1, + RGBA6ToRGB8 = 2, + RGBA6ToRGB565 = 3, + RGB565ToRGB8 = 4, + RGB565ToRGBA6 = 5 +}; +constexpr u32 NUM_EFB_REINTERPRET_TYPES = 6; + +inline bool AddressRangesOverlap(u32 aLower, u32 aUpper, u32 bLower, u32 bUpper) +{ + return !((aLower >= bUpper) || (bLower >= aUpper)); +} + +class FramebufferManager final +{ +public: + FramebufferManager(); + virtual ~FramebufferManager(); + + // Does not require the framebuffer to be created. Slower than direct queries. + static AbstractTextureFormat GetEFBColorFormat(); + static AbstractTextureFormat GetEFBDepthFormat(); + static TextureConfig GetEFBColorTextureConfig(); + static TextureConfig GetEFBDepthTextureConfig(); + + // Accessors. + AbstractTexture* GetEFBColorTexture() const { return m_efb_color_texture.get(); } + AbstractTexture* GetEFBDepthTexture() const { return m_efb_depth_texture.get(); } + AbstractFramebuffer* GetEFBFramebuffer() const { return m_efb_framebuffer.get(); } + u32 GetEFBWidth() const { return m_efb_color_texture->GetWidth(); } + u32 GetEFBHeight() const { return m_efb_color_texture->GetHeight(); } + u32 GetEFBLayers() const { return m_efb_color_texture->GetLayers(); } + u32 GetEFBSamples() const { return m_efb_color_texture->GetSamples(); } + bool IsEFBMultisampled() const { return m_efb_color_texture->IsMultisampled(); } + bool IsEFBStereo() const { return m_efb_color_texture->GetLayers() > 1; } + FramebufferState GetEFBFramebufferState() const; + + // First-time setup. + bool Initialize(); + + // Recreate EFB framebuffers, call when the EFB size (IR) changes. + void RecreateEFBFramebuffer(); + + // Recompile shaders, use when MSAA mode changes. + void RecompileShaders(); + + // This is virtual, because D3D has both normalized and integer framebuffers. + void BindEFBFramebuffer(); + + // Resolve color/depth textures to a non-msaa texture, and return it. + AbstractTexture* ResolveEFBColorTexture(const MathUtil::Rectangle<int>& region); + AbstractTexture* ResolveEFBDepthTexture(const MathUtil::Rectangle<int>& region); + + // Reinterpret pixel format of EFB color texture. + // Assumes no render pass is currently in progress. + // Swaps EFB framebuffers, so re-bind afterwards. + bool ReinterpretPixelData(EFBReinterpretType convtype); + + // Clears the EFB using shaders. + void ClearEFB(const MathUtil::Rectangle<int>& rc, bool clear_color, bool clear_alpha, + bool clear_z, u32 color, u32 z); + + // Reads a framebuffer value back from the GPU. This may block if the cache is not current. + u32 PeekEFBColor(u32 x, u32 y); + float PeekEFBDepth(u32 x, u32 y); + void InvalidatePeekCache(); + + // Writes a value to the framebuffer. This will never block, and writes will be batched. + void PokeEFBColor(u32 x, u32 y, u32 color); + void PokeEFBDepth(u32 x, u32 y, float depth); + void FlushEFBPokes(); + +protected: + struct EFBPokeVertex + { + float position[4]; + u32 color; + }; + static_assert(std::is_standard_layout<EFBPokeVertex>::value, "EFBPokeVertex is standard-layout"); + + bool CreateEFBFramebuffer(); + void DestroyEFBFramebuffer(); + + bool CompileConversionPipelines(); + void DestroyConversionPipelines(); + + bool CompileReadbackPipelines(); + void DestroyReadbackPipelines(); + + bool CreateReadbackFramebuffer(); + void DestroyReadbackFramebuffer(); + + bool CompileClearPipelines(); + void DestroyClearPipelines(); + + bool CompilePokePipelines(); + void DestroyPokePipelines(); + + bool PopulateColorReadbackTexture(); + bool PopulateDepthReadbackTexture(); + + void CreatePokeVertices(std::vector<EFBPokeVertex>* destination_list, u32 x, u32 y, float z, + u32 color); + + void DrawPokeVertices(const EFBPokeVertex* vertices, u32 vertex_count, + const AbstractPipeline* pipeline); + + std::unique_ptr<AbstractTexture> m_efb_color_texture; + std::unique_ptr<AbstractTexture> m_efb_convert_color_texture; + std::unique_ptr<AbstractTexture> m_efb_depth_texture; + std::unique_ptr<AbstractTexture> m_efb_resolve_color_texture; + std::unique_ptr<AbstractTexture> m_efb_depth_resolve_texture; + + std::unique_ptr<AbstractFramebuffer> m_efb_framebuffer; + std::unique_ptr<AbstractFramebuffer> m_efb_convert_framebuffer; + std::unique_ptr<AbstractFramebuffer> m_efb_depth_resolve_framebuffer; + std::unique_ptr<AbstractPipeline> m_efb_depth_resolve_pipeline; + + // Format conversion shaders + std::array<std::unique_ptr<AbstractPipeline>, 6> m_format_conversion_pipelines; + + // EFB readback texture + std::unique_ptr<AbstractTexture> m_color_copy_texture; + std::unique_ptr<AbstractTexture> m_depth_copy_texture; + std::unique_ptr<AbstractFramebuffer> m_color_copy_framebuffer; + std::unique_ptr<AbstractFramebuffer> m_depth_copy_framebuffer; + std::unique_ptr<AbstractPipeline> m_color_copy_pipeline; + std::unique_ptr<AbstractPipeline> m_depth_copy_pipeline; + + // CPU-side EFB readback texture + std::unique_ptr<AbstractStagingTexture> m_color_readback_texture; + std::unique_ptr<AbstractStagingTexture> m_depth_readback_texture; + bool m_color_readback_texture_valid = false; + bool m_depth_readback_texture_valid = false; + + // EFB clear pipelines + // Indexed by [color_write_enabled][alpha_write_enabled][depth_write_enabled] + std::array<std::array<std::array<std::unique_ptr<AbstractPipeline>, 2>, 2>, 2> + m_efb_clear_pipelines; + + // EFB poke drawing setup + std::unique_ptr<NativeVertexFormat> m_poke_vertex_format; + std::unique_ptr<AbstractPipeline> m_color_poke_pipeline; + std::unique_ptr<AbstractPipeline> m_depth_poke_pipeline; + std::vector<EFBPokeVertex> m_color_poke_vertices; + std::vector<EFBPokeVertex> m_depth_poke_vertices; +}; + +extern std::unique_ptr<FramebufferManager> g_framebuffer_manager; diff --git a/Source/Core/VideoCommon/FramebufferManagerBase.cpp b/Source/Core/VideoCommon/FramebufferManagerBase.cpp deleted file mode 100644 index 530aac7584..0000000000 --- a/Source/Core/VideoCommon/FramebufferManagerBase.cpp +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2010 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include "VideoCommon/FramebufferManagerBase.h" - -#include <memory> - -#include "VideoCommon/AbstractTexture.h" -#include "VideoCommon/DriverDetails.h" -#include "VideoCommon/RenderBase.h" - -std::unique_ptr<FramebufferManagerBase> g_framebuffer_manager; - -unsigned int FramebufferManagerBase::m_EFBLayers = 1; - -FramebufferManagerBase::~FramebufferManagerBase() = default; - -AbstractTextureFormat FramebufferManagerBase::GetEFBDepthFormat() -{ - // 32-bit depth clears are broken in the Adreno Vulkan driver, and have no effect. - // To work around this, we use a D24_S8 buffer instead, which results in a loss of accuracy. - // We still resolve this to a R32F texture, as there is no 24-bit format. - if (DriverDetails::HasBug(DriverDetails::BUG_BROKEN_D32F_CLEAR)) - return AbstractTextureFormat::D24_S8; - else - return AbstractTextureFormat::D32F; -} diff --git a/Source/Core/VideoCommon/FramebufferManagerBase.h b/Source/Core/VideoCommon/FramebufferManagerBase.h deleted file mode 100644 index ff9ff0cc2e..0000000000 --- a/Source/Core/VideoCommon/FramebufferManagerBase.h +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2010 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once - -#include <memory> - -#include "Common/CommonTypes.h" - -enum class AbstractTextureFormat : u32; - -inline bool AddressRangesOverlap(u32 aLower, u32 aUpper, u32 bLower, u32 bUpper) -{ - return !((aLower >= bUpper) || (bLower >= aUpper)); -} - -class FramebufferManagerBase -{ -public: - virtual ~FramebufferManagerBase(); - - static unsigned int GetEFBLayers() { return m_EFBLayers; } - static AbstractTextureFormat GetEFBDepthFormat(); - -protected: - static unsigned int m_EFBLayers; -}; - -extern std::unique_ptr<FramebufferManagerBase> g_framebuffer_manager; diff --git a/Source/Core/VideoCommon/FramebufferShaderGen.cpp b/Source/Core/VideoCommon/FramebufferShaderGen.cpp new file mode 100644 index 0000000000..de7bdf3ee8 --- /dev/null +++ b/Source/Core/VideoCommon/FramebufferShaderGen.cpp @@ -0,0 +1,464 @@ +#include "VideoCommon/FramebufferShaderGen.h" +#include <sstream> +#include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/VertexShaderGen.h" + +namespace FramebufferShaderGen +{ +static APIType GetAPIType() +{ + return g_ActiveConfig.backend_info.api_type; +} + +static void EmitUniformBufferDeclaration(std::stringstream& ss) +{ + if (GetAPIType() == APIType::D3D) + ss << "cbuffer UBO : register(b0)\n"; + else + ss << "UBO_BINDING(std140, 1) uniform UBO\n"; +} + +static void EmitSamplerDeclarations(std::stringstream& ss, u32 start = 0, u32 end = 1, + bool multisampled = false) +{ + switch (GetAPIType()) + { + case APIType::D3D: + { + for (u32 i = start; i < end; i++) + { + ss << (multisampled ? "Texture2DMSArray<float4>" : "Texture2DArray<float4>") << " tex" << i + << " : register(t" << i << ");\n"; + ss << "SamplerState" + << " samp" << i << " : register(s" << i << ");\n"; + } + } + break; + + case APIType::OpenGL: + case APIType::Vulkan: + { + for (u32 i = start; i < end; i++) + { + ss << "SAMPLER_BINDING(" << i << ") uniform " + << (multisampled ? "sampler2DMSArray" : "sampler2DArray") << " samp" << i << ";\n"; + } + } + break; + default: + break; + } +} + +static void EmitSampleTexture(std::stringstream& ss, u32 n, const char* coords) +{ + switch (GetAPIType()) + { + case APIType::D3D: + ss << "tex" << n << ".Sample(samp" << n << ", " << coords << ")"; + break; + + case APIType::OpenGL: + case APIType::Vulkan: + ss << "texture(samp" << n << ", " << coords << ")"; + break; + + default: + break; + } +} + +static void EmitVertexMainDeclaration(std::stringstream& ss, u32 num_tex_inputs, + u32 num_color_inputs, bool position_input, + u32 num_tex_outputs, u32 num_color_outputs, + const char* extra_inputs = "") +{ + switch (GetAPIType()) + { + case APIType::D3D: + { + ss << "void main("; + for (u32 i = 0; i < num_tex_inputs; i++) + ss << "in float3 rawtex" << i << " : TEXCOORD" << i << ", "; + for (u32 i = 0; i < num_color_inputs; i++) + ss << "in float4 rawcolor" << i << " : COLOR" << i << ", "; + if (position_input) + ss << "in float4 rawpos : POSITION, "; + ss << extra_inputs; + for (u32 i = 0; i < num_tex_outputs; i++) + ss << "out float3 v_tex" << i << " : TEXCOORD" << i << ", "; + for (u32 i = 0; i < num_color_outputs; i++) + ss << "out float4 v_col" << i << " : COLOR" << i << ", "; + ss << "out float4 opos : SV_Position)\n"; + } + break; + + case APIType::OpenGL: + case APIType::Vulkan: + { + for (u32 i = 0; i < num_tex_inputs; i++) + ss << "ATTRIBUTE_LOCATION(" << (SHADER_TEXTURE0_ATTRIB + i) << ") in float3 rawtex" << i + << ";\n"; + for (u32 i = 0; i < num_color_inputs; i++) + ss << "ATTRIBUTE_LOCATION(" << (SHADER_COLOR0_ATTRIB + i) << ") in float4 rawcolor" << i + << ";\n"; + if (position_input) + ss << "ATTRIBUTE_LOCATION(" << SHADER_POSITION_ATTRIB << ") in float4 rawpos;\n"; + for (u32 i = 0; i < num_tex_outputs; i++) + ss << "VARYING_LOCATION(" << i << ") out float3 v_tex" << i << ";\n"; + for (u32 i = 0; i < num_color_outputs; i++) + ss << "VARYING_LOCATION(" << (num_tex_inputs + i) << ") out float4 v_col" << i << ";\n"; + ss << "#define opos gl_Position\n"; + ss << extra_inputs << "\n"; + ss << "void main()\n"; + } + break; + default: + break; + } +} + +static void EmitPixelMainDeclaration(std::stringstream& ss, u32 num_tex_inputs, + u32 num_color_inputs, const char* output_type = "float4", + const char* extra_vars = "") +{ + switch (GetAPIType()) + { + case APIType::D3D: + { + ss << "void main("; + for (u32 i = 0; i < num_tex_inputs; i++) + ss << "in float3 v_tex" << i << " : TEXCOORD" << i << ", "; + for (u32 i = 0; i < num_color_inputs; i++) + ss << "in float4 v_col" << i << " : COLOR" << i << ", "; + ss << extra_vars << "out " << output_type << " ocol0 : SV_Target)\n"; + } + break; + + case APIType::OpenGL: + case APIType::Vulkan: + { + for (u32 i = 0; i < num_tex_inputs; i++) + ss << "VARYING_LOCATION(" << i << ") in float3 v_tex" << i << ";\n"; + for (u32 i = 0; i < num_color_inputs; i++) + ss << "VARYING_LOCATION(" << (num_tex_inputs + i) << ") in float4 v_col" << i << ";\n"; + ss << "FRAGMENT_OUTPUT_LOCATION(0) out " << output_type << " ocol0;\n"; + ss << extra_vars << "\n"; + ss << "void main()\n"; + } + break; + + default: + break; + } +} + +std::string GenerateScreenQuadVertexShader() +{ + std::stringstream ss; + EmitVertexMainDeclaration(ss, 0, 0, false, 1, 0, + GetAPIType() == APIType::D3D ? "in uint id : SV_VertexID, " : + "#define id gl_VertexID\n"); + ss << "{\n"; + ss << " v_tex0 = float3(float((id << 1) & 2), float(id & 2), 0.0f);\n"; + ss << " opos = float4(v_tex0.xy * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);\n"; + + // NDC space is flipped in Vulkan. We also flip in GL so that (0,0) is in the lower-left. + if (GetAPIType() == APIType::Vulkan || GetAPIType() == APIType::OpenGL) + ss << " opos.y = -opos.y;\n"; + + ss << "}\n"; + + return ss.str(); +} + +std::string GeneratePassthroughGeometryShader(u32 num_tex, u32 num_colors) +{ + std::stringstream ss; + if (GetAPIType() == APIType::D3D) + { + ss << "struct VS_OUTPUT\n"; + ss << "{\n"; + for (u32 i = 0; i < num_tex; i++) + ss << " float3 tex" << i << " : TEXCOORD" << i << ";\n"; + for (u32 i = 0; i < num_colors; i++) + ss << " float4 color" << i << " : COLOR" << i << ";\n"; + ss << " float4 position : SV_Position;\n"; + ss << "};\n"; + ss << "struct GS_OUTPUT\n"; + ss << "{"; + for (u32 i = 0; i < num_tex; i++) + ss << " float3 tex" << i << " : TEXCOORD" << i << ";\n"; + for (u32 i = 0; i < num_colors; i++) + ss << " float4 color" << i << " : COLOR" << i << ";\n"; + ss << " float4 position : SV_Position;\n"; + ss << " uint slice : SV_RenderTargetArrayIndex;\n"; + ss << "};\n\n"; + ss << "[maxvertexcount(6)]\n"; + ss << "void main(triangle VS_OUTPUT vso[3], inout TriangleStream<GS_OUTPUT> output)\n"; + ss << "{\n"; + ss << " for (uint slice = 0; slice < 2u; slice++)\n"; + ss << " {\n"; + ss << " for (int i = 0; i < 3; i++)\n"; + ss << " {\n"; + ss << " GS_OUTPUT gso;\n"; + ss << " gso.position = vso[i].position;\n"; + for (u32 i = 0; i < num_tex; i++) + ss << " gso.tex" << i << " = float3(vso[i].tex" << i << ".xy, float(slice));\n"; + for (u32 i = 0; i < num_colors; i++) + ss << " gso.color" << i << " = vso[i].color" << i << ";\n"; + ss << " gso.slice = slice;\n"; + ss << " output.Append(gso);\n"; + ss << " }\n"; + ss << " output.RestartStrip();\n"; + ss << " }\n"; + ss << "}\n"; + } + else if (GetAPIType() == APIType::OpenGL || GetAPIType() == APIType::Vulkan) + { + ss << "layout(triangles) in;\n"; + ss << "layout(triangle_strip, max_vertices = 6) out;\n"; + for (u32 i = 0; i < num_tex; i++) + { + ss << "layout(location = " << i << ") in float3 v_tex" << i << "[];\n"; + ss << "layout(location = " << i << ") out float3 out_tex" << i << ";\n"; + } + for (u32 i = 0; i < num_colors; i++) + { + ss << "layout(location = " << (num_tex + i) << ") in float4 v_col" << i << "[];\n"; + ss << "layout(location = " << (num_tex + i) << ") out float4 out_col" << i << ";\n"; + } + ss << "\n"; + ss << "void main()\n"; + ss << "{\n"; + ss << " for (int j = 0; j < 2; j++)\n"; + ss << " {\n"; + ss << " gl_Layer = j;\n"; + + // We have to explicitly unroll this loop otherwise the GL compiler gets cranky. + for (u32 v = 0; v < 3; v++) + { + ss << " gl_Position = gl_in[" << v << "].gl_Position;\n"; + for (u32 i = 0; i < num_tex; i++) + ss << " out_tex" << i << " = float3(v_tex" << i << "[" << v << "].xy, float(j));\n"; + for (u32 i = 0; i < num_colors; i++) + ss << " out_col" << i << " = v_col" << i << "[" << v << "];\n"; + ss << " EmitVertex();\n\n"; + } + ss << " EndPrimitive();\n"; + ss << " }\n"; + ss << "}\n"; + } + + return ss.str(); +} + +std::string GenerateTextureCopyVertexShader() +{ + std::stringstream ss; + EmitUniformBufferDeclaration(ss); + ss << "{"; + ss << " float2 src_offset;\n"; + ss << " float2 src_size;\n"; + ss << "};\n\n"; + + EmitVertexMainDeclaration(ss, 0, 0, false, 1, 0, + GetAPIType() == APIType::D3D ? "in uint id : SV_VertexID, " : + "#define id gl_VertexID"); + ss << "{\n"; + ss << " v_tex0 = float3(float((id << 1) & 2), float(id & 2), 0.0f);\n"; + ss << " opos = float4(v_tex0.xy * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);\n"; + ss << " v_tex0 = float3(src_offset + (src_size * v_tex0.xy), 0.0f);\n"; + + // NDC space is flipped in Vulkan. We also flip in GL so that (0,0) is in the lower-left. + if (GetAPIType() == APIType::Vulkan || GetAPIType() == APIType::OpenGL) + ss << " opos.y = -opos.y;\n"; + + ss << "}\n"; + + return ss.str(); +} + +std::string GenerateTextureCopyPixelShader() +{ + std::stringstream ss; + EmitSamplerDeclarations(ss, 0, 1, false); + EmitPixelMainDeclaration(ss, 1, 0); + ss << "{\n"; + ss << " ocol0 = "; + EmitSampleTexture(ss, 0, "v_tex0"); + ss << ";\n"; + ss << "}\n"; + return ss.str(); +} + +std::string GenerateColorPixelShader() +{ + std::stringstream ss; + EmitPixelMainDeclaration(ss, 0, 1); + ss << "{\n"; + ss << " ocol0 = v_col0;\n"; + ss << "}\n"; + return ss.str(); +} + +std::string GenerateResolveDepthPixelShader(u32 samples) +{ + std::stringstream ss; + EmitSamplerDeclarations(ss, 0, 1, true); + EmitPixelMainDeclaration(ss, 1, 0, "float", + GetAPIType() == APIType::D3D ? "in float4 ipos : SV_Position, " : ""); + ss << "{\n"; + ss << " int layer = int(v_tex0.z);\n"; + if (GetAPIType() == APIType::D3D) + ss << " int3 coords = int3(int2(ipos.xy), layer);\n"; + else + ss << " int3 coords = int3(int2(gl_FragCoord.xy), layer);\n"; + + // Take the minimum of all depth samples. + if (GetAPIType() == APIType::D3D) + ss << " ocol0 = tex0.Load(coords, 0).r;\n"; + else + ss << " ocol0 = texelFetch(samp0, coords, 0).r;\n"; + ss << " for (int i = 1; i < " << samples << "; i++)\n"; + if (GetAPIType() == APIType::D3D) + ss << " ocol0 = min(ocol0, tex0.Load(coords, i).r);\n"; + else + ss << " ocol0 = min(ocol0, texelFetch(samp0, coords, i).r);\n"; + + ss << "}\n"; + return ss.str(); +} + +std::string GenerateClearVertexShader() +{ + std::stringstream ss; + EmitUniformBufferDeclaration(ss); + ss << "{\n"; + ss << " float4 clear_color;\n"; + ss << " float clear_depth;\n"; + ss << "};\n"; + + EmitVertexMainDeclaration(ss, 0, 0, false, 0, 1, + GetAPIType() == APIType::D3D ? "in uint id : SV_VertexID, " : + "#define id gl_VertexID\n"); + ss << "{\n"; + ss << " float2 coord = float2(float((id << 1) & 2), float(id & 2));\n"; + ss << " opos = float4(coord * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), clear_depth, 1.0f);\n"; + ss << " v_col0 = clear_color;\n"; + + // NDC space is flipped in Vulkan + if (GetAPIType() == APIType::Vulkan) + ss << " opos.y = -opos.y;\n"; + + ss << "}\n"; + + return ss.str(); +} + +std::string GenerateEFBPokeVertexShader() +{ + std::stringstream ss; + EmitVertexMainDeclaration(ss, 0, 1, true, 0, 1); + ss << "{\n"; + ss << " v_col0 = rawcolor0;\n"; + ss << " opos = float4(rawpos.xyz, 1.0f);\n"; + if (g_ActiveConfig.backend_info.bSupportsLargePoints) + ss << " gl_PointSize = rawpos.w;\n"; + + // NDC space is flipped in Vulkan. + if (GetAPIType() == APIType::Vulkan) + ss << " opos.y = -opos.y;\n"; + + ss << "}\n"; + return ss.str(); +} + +std::string GenerateFormatConversionShader(EFBReinterpretType convtype, u32 samples) +{ + std::stringstream ss; + EmitSamplerDeclarations(ss, 0, 1, samples > 1); + EmitPixelMainDeclaration(ss, 1, 0, "float4", + GetAPIType() == APIType::D3D ? + "in float4 ipos : SV_Position, in uint isample : SV_SampleIndex, " : + ""); + ss << "{\n"; + ss << " int layer = int(v_tex0.z);\n"; + if (GetAPIType() == APIType::D3D) + ss << " int3 coords = int3(int2(ipos.xy), layer);\n"; + else + ss << " int3 coords = int3(int2(gl_FragCoord.xy), layer);\n"; + + if (samples == 1) + { + // No MSAA at all. + if (GetAPIType() == APIType::D3D) + ss << " float4 val = tex0.Load(int4(coords, 0));\n"; + else + ss << " float4 val = texelFetch(samp0, coords, 0);\n"; + } + else if (g_ActiveConfig.bSSAA) + { + // Sample shading, shader runs once per sample + if (GetAPIType() == APIType::D3D) + ss << " float4 val = tex0.Load(coords, isample);"; + else + ss << " float4 val = texelFetch(samp0, coords, gl_SampleID);"; + } + else + { + // MSAA without sample shading, average out all samples. + ss << " float4 val = float4(0.0f, 0.0f, 0.0f, 0.0f);\n"; + ss << " for (int i = 0; i < " << samples << "; i++)\n"; + if (GetAPIType() == APIType::D3D) + ss << " val += tex0.Load(coords, i);\n"; + else + ss << " val += texelFetch(samp0, coords, i);\n"; + ss << " val /= float(" << samples << ");\n"; + } + + switch (convtype) + { + case EFBReinterpretType::RGB8ToRGBA6: + ss << " int4 src8 = int4(round(val * 255.f));\n"; + ss << " int4 dst6;\n"; + ss << " dst6.r = src8.r >> 2;\n"; + ss << " dst6.g = ((src8.r & 0x3) << 4) | (src8.g >> 4);\n"; + ss << " dst6.b = ((src8.g & 0xF) << 2) | (src8.b >> 6);\n"; + ss << " dst6.a = src8.b & 0x3F;\n"; + ss << " ocol0 = float4(dst6) / 63.f;\n"; + break; + + case EFBReinterpretType::RGB8ToRGB565: + ss << " ocol0 = val;\n"; + break; + + case EFBReinterpretType::RGBA6ToRGB8: + ss << " int4 src6 = int4(round(val * 63.f));\n"; + ss << " int4 dst8;\n"; + ss << " dst8.r = (src6.r << 2) | (src6.g >> 4);\n"; + ss << " dst8.g = ((src6.g & 0xF) << 4) | (src6.b >> 2);\n"; + ss << " dst8.b = ((src6.b & 0x3) << 6) | src6.a;\n"; + ss << " dst8.a = 255;\n"; + ss << " ocol0 = float4(dst8) / 255.f;\n"; + break; + + case EFBReinterpretType::RGBA6ToRGB565: + ss << " ocol0 = val;\n"; + break; + + case EFBReinterpretType::RGB565ToRGB8: + ss << " ocol0 = val;\n"; + break; + + case EFBReinterpretType::RGB565ToRGBA6: + // + ss << " ocol0 = val;\n"; + break; + } + + ss << "}\n"; + return ss.str(); +} + +} // namespace FramebufferShaderGen diff --git a/Source/Core/VideoCommon/FramebufferShaderGen.h b/Source/Core/VideoCommon/FramebufferShaderGen.h new file mode 100644 index 0000000000..0e065521cf --- /dev/null +++ b/Source/Core/VideoCommon/FramebufferShaderGen.h @@ -0,0 +1,32 @@ +#pragma once +#include <string> +#include "VideoCommon/VideoCommon.h" + +enum class EFBReinterpretType; + +namespace FramebufferShaderGen +{ +struct Config +{ + Config(APIType api_type_, u32 efb_samples_, u32 efb_layers_, bool ssaa_) + : api_type(api_type_), efb_samples(efb_samples_), efb_layers(efb_layers_), ssaa(ssaa_) + { + } + + APIType api_type; + u32 efb_samples; + u32 efb_layers; + bool ssaa; +}; + +std::string GenerateScreenQuadVertexShader(); +std::string GeneratePassthroughGeometryShader(u32 num_tex, u32 num_colors); +std::string GenerateTextureCopyVertexShader(); +std::string GenerateTextureCopyPixelShader(); +std::string GenerateResolveDepthPixelShader(u32 samples); +std::string GenerateClearVertexShader(); +std::string GenerateEFBPokeVertexShader(); +std::string GenerateColorPixelShader(); +std::string GenerateFormatConversionShader(EFBReinterpretType convtype, u32 samples); + +} // namespace FramebufferShaderGen diff --git a/Source/Core/VideoCommon/NativeVertexFormat.h b/Source/Core/VideoCommon/NativeVertexFormat.h index 75cf841a32..b7c3477a60 100644 --- a/Source/Core/VideoCommon/NativeVertexFormat.h +++ b/Source/Core/VideoCommon/NativeVertexFormat.h @@ -104,17 +104,17 @@ struct hash<PortableVertexDeclaration> class NativeVertexFormat { public: + NativeVertexFormat(const PortableVertexDeclaration& vtx_decl) : m_decl(vtx_decl) {} virtual ~NativeVertexFormat() {} + NativeVertexFormat(const NativeVertexFormat&) = delete; NativeVertexFormat& operator=(const NativeVertexFormat&) = delete; NativeVertexFormat(NativeVertexFormat&&) = default; NativeVertexFormat& operator=(NativeVertexFormat&&) = default; - u32 GetVertexStride() const { return vtx_decl.stride; } - const PortableVertexDeclaration& GetVertexDeclaration() const { return vtx_decl; } + u32 GetVertexStride() const { return m_decl.stride; } + const PortableVertexDeclaration& GetVertexDeclaration() const { return m_decl; } protected: - // Let subclasses construct. - NativeVertexFormat() {} - PortableVertexDeclaration vtx_decl; + PortableVertexDeclaration m_decl; }; diff --git a/Source/Core/VideoCommon/PixelShaderGen.cpp b/Source/Core/VideoCommon/PixelShaderGen.cpp index 9fbbb2cda4..9936bcb85c 100644 --- a/Source/Core/VideoCommon/PixelShaderGen.cpp +++ b/Source/Core/VideoCommon/PixelShaderGen.cpp @@ -171,8 +171,7 @@ PixelShaderUid GetPixelShaderUid() uid_data->genMode_numindstages = bpmem.genMode.numindstages; uid_data->genMode_numtevstages = bpmem.genMode.numtevstages; uid_data->genMode_numtexgens = bpmem.genMode.numtexgens; - uid_data->bounding_box = g_ActiveConfig.BBoxUseFragmentShaderImplementation() && - g_ActiveConfig.bBBoxEnable && BoundingBox::active; + uid_data->bounding_box = g_ActiveConfig.bBBoxEnable && BoundingBox::active; uid_data->rgba6_format = bpmem.zcontrol.pixel_format == PEControl::RGBA6_Z24 && !g_ActiveConfig.bForceTrueColor; uid_data->dither = bpmem.blendmode.dither && uid_data->rgba6_format; @@ -456,10 +455,6 @@ void WritePixelShaderCommonHeader(ShaderCode& out, APIType ApiType, u32 num_texg out.Write("globallycoherent RWBuffer<int> bbox_data : register(u2);\n"); } } - - out.Write("struct VS_OUTPUT {\n"); - GenerateVSOutputMembers(out, ApiType, num_texgens, host_config, ""); - out.Write("};\n"); } static void WriteStage(ShaderCode& out, const pixel_shader_uid_data* uid_data, int n, @@ -804,7 +799,7 @@ ShaderCode GeneratePixelShaderCode(APIType ApiType, const ShaderHostConfig& host } else { - if (ApiType == APIType::D3D || ApiType == APIType::Vulkan) + if (!host_config.backend_reversed_depth_range) out.Write("\tint zCoord = int((1.0 - rawpos.z) * 16777216.0);\n"); else out.Write("\tint zCoord = int(rawpos.z * 16777216.0);\n"); @@ -818,7 +813,7 @@ ShaderCode GeneratePixelShaderCode(APIType ApiType, const ShaderHostConfig& host // Note: z-textures are not written to depth buffer if early depth test is used if (uid_data->per_pixel_depth && uid_data->early_ztest) { - if (ApiType == APIType::D3D || ApiType == APIType::Vulkan) + if (!host_config.backend_reversed_depth_range) out.Write("\tdepth = 1.0 - float(zCoord) / 16777216.0;\n"); else out.Write("\tdepth = float(zCoord) / 16777216.0;\n"); @@ -839,7 +834,7 @@ ShaderCode GeneratePixelShaderCode(APIType ApiType, const ShaderHostConfig& host if (uid_data->per_pixel_depth && uid_data->late_ztest) { - if (ApiType == APIType::D3D || ApiType == APIType::Vulkan) + if (!host_config.backend_reversed_depth_range) out.Write("\tdepth = 1.0 - float(zCoord) / 16777216.0;\n"); else out.Write("\tdepth = float(zCoord) / 16777216.0;\n"); @@ -1316,7 +1311,7 @@ static void WriteAlphaTest(ShaderCode& out, const pixel_shader_uid_data* uid_dat if (per_pixel_depth) { out.Write("\t\tdepth = %s;\n", - (ApiType == APIType::D3D || ApiType == APIType::Vulkan) ? "0.0" : "1.0"); + !g_ActiveConfig.backend_info.bSupportsReversedDepthRange ? "0.0" : "1.0"); } // ZCOMPLOC HACK: diff --git a/Source/Core/VideoCommon/PixelShaderManager.cpp b/Source/Core/VideoCommon/PixelShaderManager.cpp index 3ff6f9f073..7799a0abcb 100644 --- a/Source/Core/VideoCommon/PixelShaderManager.cpp +++ b/Source/Core/VideoCommon/PixelShaderManager.cpp @@ -522,9 +522,7 @@ void PixelShaderManager::SetBlendModeChanged() void PixelShaderManager::SetBoundingBoxActive(bool active) { - const bool enable = - active && g_ActiveConfig.bBBoxEnable && g_ActiveConfig.BBoxUseFragmentShaderImplementation(); - + const bool enable = active && g_ActiveConfig.bBBoxEnable; if (enable == (constants.bounding_box != 0)) return; diff --git a/Source/Core/VideoCommon/PostProcessing.cpp b/Source/Core/VideoCommon/PostProcessing.cpp index a83469e451..b1c9c42056 100644 --- a/Source/Core/VideoCommon/PostProcessing.cpp +++ b/Source/Core/VideoCommon/PostProcessing.cpp @@ -5,74 +5,44 @@ #include <sstream> #include <string> +#include "Common/Assert.h" #include "Common/CommonPaths.h" #include "Common/CommonTypes.h" #include "Common/FileSearch.h" #include "Common/FileUtil.h" #include "Common/IniFile.h" #include "Common/Logging/Log.h" +#include "Common/MsgHandler.h" #include "Common/StringUtil.h" +#include "VideoCommon/AbstractFramebuffer.h" +#include "VideoCommon/AbstractPipeline.h" +#include "VideoCommon/AbstractShader.h" +#include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/FramebufferManager.h" #include "VideoCommon/PostProcessing.h" +#include "VideoCommon/RenderBase.h" +#include "VideoCommon/ShaderCache.h" +#include "VideoCommon/VertexManagerBase.h" #include "VideoCommon/VideoConfig.h" -static const char s_default_shader[] = "void main() { SetOutput(Sample()); }\n"; - -PostProcessingShaderImplementation::PostProcessingShaderImplementation() -{ - m_timer.Start(); -} - -PostProcessingShaderImplementation::~PostProcessingShaderImplementation() -{ - m_timer.Stop(); -} - -static std::vector<std::string> GetShaders(const std::string& sub_dir = "") -{ - std::vector<std::string> paths = - Common::DoFileSearch({File::GetUserPath(D_SHADERS_IDX) + sub_dir, - File::GetSysDirectory() + SHADERS_DIR DIR_SEP + sub_dir}, - {".glsl"}); - std::vector<std::string> result; - for (std::string path : paths) - { - std::string name; - SplitPath(path, nullptr, &name, nullptr); - result.push_back(name); - } - return result; -} - -std::vector<std::string> PostProcessingShaderImplementation::GetShaderList(APIType api_type) +namespace VideoCommon { - // Currently there is no differentiation between API types and shader languages. - // This could change in the future, hence the api_type parameter, but ideally, - // shaders should be compatible across backends. - if (api_type == APIType::OpenGL || api_type == APIType::Vulkan) - return GetShaders(); - - return {}; -} - -std::vector<std::string> PostProcessingShaderImplementation::GetAnaglyphShaderList(APIType api_type) -{ - if (api_type == APIType::OpenGL || api_type == APIType::Vulkan) - return GetShaders(ANAGLYPH_DIR DIR_SEP); - - return {}; -} +static const char s_default_shader[] = "void main() { SetOutput(Sample()); }\n"; -PostProcessingShaderConfiguration::PostProcessingShaderConfiguration() = default; +PostProcessingConfiguration::PostProcessingConfiguration() = default; -PostProcessingShaderConfiguration::~PostProcessingShaderConfiguration() = default; +PostProcessingConfiguration::~PostProcessingConfiguration() = default; -std::string PostProcessingShaderConfiguration::LoadShader(std::string shader) +void PostProcessingConfiguration::LoadShader(const std::string& shader) { // Load the shader from the configuration if there isn't one sent to us. - if (shader.empty()) - shader = g_ActiveConfig.sPostProcessingShader; m_current_shader = shader; + if (shader.empty()) + { + LoadDefaultShader(); + return; + } const std::string sub_dir = (g_Config.stereo_mode == StereoMode::Anaglyph) ? ANAGLYPH_DIR DIR_SEP : ""; @@ -81,32 +51,32 @@ std::string PostProcessingShaderConfiguration::LoadShader(std::string shader) std::string code; std::string path = File::GetUserPath(D_SHADERS_IDX) + sub_dir + shader + ".glsl"; - if (shader.empty()) + if (!File::Exists(path)) { - code = s_default_shader; + // Fallback to shared user dir + path = File::GetSysDirectory() + SHADERS_DIR DIR_SEP + sub_dir + shader + ".glsl"; } - else - { - if (!File::Exists(path)) - { - // Fallback to shared user dir - path = File::GetSysDirectory() + SHADERS_DIR DIR_SEP + sub_dir + shader + ".glsl"; - } - if (!File::ReadFileToString(path, code)) - { - ERROR_LOG(VIDEO, "Post-processing shader not found: %s", path.c_str()); - code = s_default_shader; - } + if (!File::ReadFileToString(path, code)) + { + ERROR_LOG(VIDEO, "Post-processing shader not found: %s", path.c_str()); + LoadDefaultShader(); + return; } LoadOptions(code); LoadOptionsConfiguration(); + m_current_shader_code = code; +} - return code; +void PostProcessingConfiguration::LoadDefaultShader() +{ + m_options.clear(); + m_any_options_dirty = false; + m_current_shader_code = s_default_shader; } -void PostProcessingShaderConfiguration::LoadOptions(const std::string& code) +void PostProcessingConfiguration::LoadOptions(const std::string& code) { const std::string config_start_delimiter = "[configuration]"; const std::string config_end_delimiter = "[/configuration]"; @@ -254,7 +224,7 @@ void PostProcessingShaderConfiguration::LoadOptions(const std::string& code) } } -void PostProcessingShaderConfiguration::LoadOptionsConfiguration() +void PostProcessingConfiguration::LoadOptionsConfiguration() { IniFile ini; ini.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX)); @@ -288,7 +258,7 @@ void PostProcessingShaderConfiguration::LoadOptionsConfiguration() } } -void PostProcessingShaderConfiguration::SaveOptionsConfiguration() +void PostProcessingConfiguration::SaveOptionsConfiguration() { IniFile ini; ini.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX)); @@ -331,13 +301,7 @@ void PostProcessingShaderConfiguration::SaveOptionsConfiguration() ini.Save(File::GetUserPath(F_DOLPHINCONFIG_IDX)); } -void PostProcessingShaderConfiguration::ReloadShader() -{ - m_current_shader = ""; -} - -void PostProcessingShaderConfiguration::SetOptionf(const std::string& option, int index, - float value) +void PostProcessingConfiguration::SetOptionf(const std::string& option, int index, float value) { auto it = m_options.find(option); @@ -346,7 +310,7 @@ void PostProcessingShaderConfiguration::SetOptionf(const std::string& option, in m_any_options_dirty = true; } -void PostProcessingShaderConfiguration::SetOptioni(const std::string& option, int index, s32 value) +void PostProcessingConfiguration::SetOptioni(const std::string& option, int index, s32 value) { auto it = m_options.find(option); @@ -355,7 +319,7 @@ void PostProcessingShaderConfiguration::SetOptioni(const std::string& option, in m_any_options_dirty = true; } -void PostProcessingShaderConfiguration::SetOptionb(const std::string& option, bool value) +void PostProcessingConfiguration::SetOptionb(const std::string& option, bool value) { auto it = m_options.find(option); @@ -363,3 +327,384 @@ void PostProcessingShaderConfiguration::SetOptionb(const std::string& option, bo it->second.m_dirty = true; m_any_options_dirty = true; } + +PostProcessing::PostProcessing() +{ + m_timer.Start(); +} + +PostProcessing::~PostProcessing() +{ + m_timer.Stop(); +} + +static std::vector<std::string> GetShaders(const std::string& sub_dir = "") +{ + std::vector<std::string> paths = + Common::DoFileSearch({File::GetUserPath(D_SHADERS_IDX) + sub_dir, + File::GetSysDirectory() + SHADERS_DIR DIR_SEP + sub_dir}, + {".glsl"}); + std::vector<std::string> result; + for (std::string path : paths) + { + std::string name; + SplitPath(path, nullptr, &name, nullptr); + result.push_back(name); + } + return result; +} + +std::vector<std::string> PostProcessing::GetShaderList() +{ + return GetShaders(); +} + +std::vector<std::string> PostProcessing::GetAnaglyphShaderList() +{ + return GetShaders(ANAGLYPH_DIR DIR_SEP); +} + +bool PostProcessing::Initialize(AbstractTextureFormat format) +{ + m_framebuffer_format = format; + if (!CompileVertexShader() || !CompilePixelShader() || !CompilePipeline()) + return false; + + return true; +} + +void PostProcessing::RecompileShader() +{ + m_pipeline.reset(); + m_pixel_shader.reset(); + if (!CompilePixelShader()) + return; + + CompilePipeline(); +} + +void PostProcessing::RecompilePipeline() +{ + m_pipeline.reset(); + CompilePipeline(); +} + +void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst, + const MathUtil::Rectangle<int>& src, + const AbstractTexture* src_tex, int src_layer) +{ + if (g_renderer->GetCurrentFramebuffer()->GetColorFormat() != m_framebuffer_format) + { + m_framebuffer_format = g_renderer->GetCurrentFramebuffer()->GetColorFormat(); + RecompilePipeline(); + } + + if (!m_pipeline) + return; + + FillUniformBuffer(src, src_tex, src_layer); + g_vertex_manager->UploadUtilityUniforms(m_uniform_staging_buffer.data(), + static_cast<u32>(m_uniform_staging_buffer.size())); + + g_renderer->SetViewportAndScissor( + g_renderer->ConvertFramebufferRectangle(dst, g_renderer->GetCurrentFramebuffer())); + g_renderer->SetPipeline(m_pipeline.get()); + g_renderer->SetTexture(0, src_tex); + g_renderer->SetSamplerState(0, RenderState::GetLinearSamplerState()); + g_renderer->Draw(0, 3); +} + +std::string PostProcessing::GetUniformBufferHeader() const +{ + std::stringstream ss; + u32 unused_counter = 1; + if (g_ActiveConfig.backend_info.api_type == APIType::D3D) + ss << "cbuffer PSBlock : register(b0) {\n"; + else + ss << "UBO_BINDING(std140, 1) uniform PSBlock {\n"; + + // Builtin uniforms + ss << " float4 resolution;\n"; + ss << " float4 src_rect;\n"; + ss << " uint time;\n"; + ss << " int layer;\n"; + for (u32 i = 0; i < 2; i++) + ss << " uint ubo_align_" << unused_counter++ << "_;\n"; + ss << "\n"; + + // Custom options/uniforms + for (const auto& it : m_config.GetOptions()) + { + if (it.second.m_type == + PostProcessingConfiguration::ConfigurationOption::OptionType::OPTION_BOOL) + { + ss << StringFromFormat(" int %s;\n", it.first.c_str()); + for (u32 i = 0; i < 3; i++) + ss << " int ubo_align_" << unused_counter++ << "_;\n"; + } + else if (it.second.m_type == + PostProcessingConfiguration::ConfigurationOption::OptionType::OPTION_INTEGER) + { + u32 count = static_cast<u32>(it.second.m_integer_values.size()); + if (count == 1) + ss << StringFromFormat(" int %s;\n", it.first.c_str()); + else + ss << StringFromFormat(" int%u %s;\n", count, it.first.c_str()); + + for (u32 i = count; i < 4; i++) + ss << " int ubo_align_" << unused_counter++ << "_;\n"; + } + else if (it.second.m_type == + PostProcessingConfiguration::ConfigurationOption::OptionType::OPTION_FLOAT) + { + u32 count = static_cast<u32>(it.second.m_float_values.size()); + if (count == 1) + ss << StringFromFormat(" float %s;\n", it.first.c_str()); + else + ss << StringFromFormat(" float%u %s;\n", count, it.first.c_str()); + + for (u32 i = count; i < 4; i++) + ss << " float ubo_align_" << unused_counter++ << "_;\n"; + } + } + + ss << "};\n\n"; + return ss.str(); +} + +std::string PostProcessing::GetHeader() const +{ + std::stringstream ss; + ss << GetUniformBufferHeader(); + if (g_ActiveConfig.backend_info.api_type == APIType::D3D) + { + ss << "Texture2DArray samp0 : register(t0);\n"; + ss << "SamplerState samp0_ss : register(s0);\n"; + } + else + { + ss << "SAMPLER_BINDING(0) uniform sampler2DArray samp0;\n"; + ss << "VARYING_LOCATION(0) in float3 v_tex0;\n"; + ss << "FRAGMENT_OUTPUT_LOCATION(0) out float4 ocol0;\n"; + } + + // Rename main, since we need to set up globals + if (g_ActiveConfig.backend_info.api_type == APIType::D3D) + { + ss << R"( +#define main real_main +static float3 v_tex0; +static float4 ocol0; + +// Wrappers for sampling functions. +#define texture(sampler, coords) sampler.Sample(sampler##_ss, coords) +#define textureOffset(sampler, coords, offset) sampler.Sample(sampler##_ss, coords, offset) +)"; + } + + ss << R"( +float4 Sample() { return texture(samp0, float3(v_tex0.xy, float(layer))); } +float4 SampleLocation(float2 location) { return texture(samp0, float3(location, float(layer))); } +float4 SampleLayer(int layer) { return texture(samp0, float3(v_tex0.xy, float(layer))); } +#define SampleOffset(offset) textureOffset(samp0, float3(v_tex0.xy, float(layer)), offset) + +float2 GetResolution() +{ + return resolution.xy; +} + +float2 GetInvResolution() +{ + return resolution.zw; +} + +float2 GetCoordinates() +{ + return v_tex0.xy; +} + +uint GetTime() +{ + return time; +} + +void SetOutput(float4 color) +{ + ocol0 = color; +} + +#define GetOption(x) (x) +#define OptionEnabled(x) ((x) != 0) + +)"; + return ss.str(); +} + +std::string PostProcessing::GetFooter() const +{ + if (g_ActiveConfig.backend_info.api_type == APIType::D3D) + { + return R"( + +#undef main +void main(in float3 v_tex0_ : TEXCOORD0, out float4 ocol0_ : SV_Target) +{ + v_tex0 = v_tex0_; + real_main(); + ocol0_ = ocol0; +})"; + } + else + { + return {}; + } +} + +bool PostProcessing::CompileVertexShader() +{ + std::stringstream ss; + ss << GetUniformBufferHeader(); + + if (g_ActiveConfig.backend_info.api_type == APIType::D3D) + { + ss << "void main(in uint id : SV_VertexID, out float3 v_tex0 : TEXCOORD0,\n"; + ss << " out float4 opos : SV_Position) {\n"; + } + else + { + ss << "VARYING_LOCATION(0) out float3 v_tex0;\n"; + ss << "#define id gl_VertexID\n"; + ss << "#define opos gl_Position\n"; + ss << "void main() {\n"; + } + ss << " v_tex0 = float3(float((id << 1) & 2), float(id & 2), 0.0f);\n"; + ss << " opos = float4(v_tex0.xy * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);\n"; + ss << " v_tex0 = float3(src_rect.xy + (src_rect.zw * v_tex0.xy), 0.0f);\n"; + + if (g_ActiveConfig.backend_info.api_type == APIType::Vulkan) + ss << " opos.y = -opos.y;\n"; + + ss << "}\n"; + + m_vertex_shader = g_renderer->CreateShaderFromSource(ShaderStage::Vertex, ss.str()); + if (!m_vertex_shader) + { + PanicAlert("Failed to compile post-processing vertex shader"); + return false; + } + + return true; +} + +struct BuiltinUniforms +{ + float resolution[4]; + float src_rect[4]; + s32 time; + u32 layer; + u32 padding[2]; +}; + +size_t PostProcessing::CalculateUniformsSize() const +{ + // Allocate a vec4 for each uniform to simplify allocation. + return sizeof(BuiltinUniforms) + m_config.GetOptions().size() * sizeof(float) * 4; +} + +void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle<int>& src, + const AbstractTexture* src_tex, int src_layer) +{ + const float rcp_src_width = 1.0f / src_tex->GetWidth(); + const float rcp_src_height = 1.0f / src_tex->GetHeight(); + BuiltinUniforms builtin_uniforms = { + {static_cast<float>(src_tex->GetWidth()), static_cast<float>(src_tex->GetHeight()), + rcp_src_width, rcp_src_height}, + {static_cast<float>(src.left) * rcp_src_width, static_cast<float>(src.top) * rcp_src_height, + static_cast<float>(src.GetWidth()) * rcp_src_width, + static_cast<float>(src.GetHeight()) * rcp_src_height}, + static_cast<s32>(m_timer.GetTimeElapsed()), + static_cast<u32>(src_layer), + }; + + u8* buf = m_uniform_staging_buffer.data(); + std::memcpy(buf, &builtin_uniforms, sizeof(builtin_uniforms)); + buf += sizeof(builtin_uniforms); + + for (const auto& it : m_config.GetOptions()) + { + union + { + u32 as_bool[4]; + s32 as_int[4]; + float as_float[4]; + } value = {}; + + switch (it.second.m_type) + { + case PostProcessingConfiguration::ConfigurationOption::OptionType::OPTION_BOOL: + value.as_bool[0] = it.second.m_bool_value ? 1 : 0; + break; + + case PostProcessingConfiguration::ConfigurationOption::OptionType::OPTION_INTEGER: + ASSERT(it.second.m_integer_values.size() < 4); + std::copy_n(it.second.m_integer_values.begin(), it.second.m_integer_values.size(), + value.as_int); + break; + + case PostProcessingConfiguration::ConfigurationOption::OptionType::OPTION_FLOAT: + ASSERT(it.second.m_float_values.size() < 4); + std::copy_n(it.second.m_float_values.begin(), it.second.m_float_values.size(), + value.as_float); + break; + } + + std::memcpy(buf, &value, sizeof(value)); + buf += sizeof(value); + } +} + +bool PostProcessing::CompilePixelShader() +{ + m_pipeline.reset(); + m_pixel_shader.reset(); + + // Generate GLSL and compile the new shader. + m_config.LoadShader(g_ActiveConfig.sPostProcessingShader); + m_pixel_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Pixel, GetHeader() + m_config.GetShaderCode() + GetFooter()); + if (!m_pixel_shader) + { + PanicAlert("Failed to compile post-processing shader %s", m_config.GetShader().c_str()); + + // Use default shader. + m_config.LoadDefaultShader(); + m_pixel_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Pixel, GetHeader() + m_config.GetShaderCode() + GetFooter()); + if (!m_pixel_shader) + return false; + } + + m_uniform_staging_buffer.resize(CalculateUniformsSize()); + return true; +} + +bool PostProcessing::CompilePipeline() +{ + AbstractPipelineConfig config = {}; + config.vertex_shader = m_vertex_shader.get(); + config.geometry_shader = g_ActiveConfig.stereo_mode == StereoMode::QuadBuffer ? + g_shader_cache->GetTexcoordGeometryShader() : + nullptr; + config.pixel_shader = m_pixel_shader.get(); + config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles); + config.depth_state = RenderState::GetNoDepthTestingDepthState(); + config.blending_state = RenderState::GetNoBlendingBlendState(); + config.framebuffer_state = RenderState::GetColorFramebufferState(m_framebuffer_format); + config.usage = AbstractPipelineUsage::Utility; + m_pipeline = g_renderer->CreatePipeline(config); + if (!m_pipeline) + return false; + + return true; +} +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/PostProcessing.h b/Source/Core/VideoCommon/PostProcessing.h index e610b7fc38..ede8adaf6e 100644 --- a/Source/Core/VideoCommon/PostProcessing.h +++ b/Source/Core/VideoCommon/PostProcessing.h @@ -10,9 +10,16 @@ #include "Common/CommonTypes.h" #include "Common/Timer.h" +#include "VideoCommon/TextureConfig.h" #include "VideoCommon/VideoCommon.h" -class PostProcessingShaderConfiguration +class AbstractTexture; +class AbstractPipeline; +class AbstractShader; + +namespace VideoCommon +{ +class PostProcessingConfiguration { public: struct ConfigurationOption @@ -48,16 +55,17 @@ public: using ConfigMap = std::map<std::string, ConfigurationOption>; - PostProcessingShaderConfiguration(); - virtual ~PostProcessingShaderConfiguration(); + PostProcessingConfiguration(); + virtual ~PostProcessingConfiguration(); // Loads the configuration with a shader // If the argument is "" the class will load the shader from the g_activeConfig option. // Returns the loaded shader source from file - std::string LoadShader(std::string shader = ""); + void LoadShader(const std::string& shader); + void LoadDefaultShader(); void SaveOptionsConfiguration(); - void ReloadShader(); const std::string& GetShader() const { return m_current_shader; } + const std::string& GetShaderCode() const { return m_current_shader_code; } bool IsDirty() const { return m_any_options_dirty; } void SetDirty(bool dirty) { m_any_options_dirty = dirty; } bool HasOptions() const { return m_options.size() > 0; } @@ -72,26 +80,53 @@ public: private: bool m_any_options_dirty = false; std::string m_current_shader; + std::string m_current_shader_code; ConfigMap m_options; void LoadOptions(const std::string& code); void LoadOptionsConfiguration(); }; -class PostProcessingShaderImplementation +class PostProcessing { public: - PostProcessingShaderImplementation(); - virtual ~PostProcessingShaderImplementation(); + PostProcessing(); + virtual ~PostProcessing(); + + static std::vector<std::string> GetShaderList(); + static std::vector<std::string> GetAnaglyphShaderList(); + + PostProcessingConfiguration* GetConfig() { return &m_config; } - static std::vector<std::string> GetShaderList(APIType api_type); - static std::vector<std::string> GetAnaglyphShaderList(APIType api_type); + bool Initialize(AbstractTextureFormat format); - PostProcessingShaderConfiguration* GetConfig() { return &m_config; } + void RecompileShader(); + void RecompilePipeline(); + + void BlitFromTexture(const MathUtil::Rectangle<int>& dst, const MathUtil::Rectangle<int>& src, + const AbstractTexture* src_tex, int src_layer); protected: + std::string GetUniformBufferHeader() const; + std::string GetHeader() const; + std::string GetFooter() const; + + bool CompileVertexShader(); + bool CompilePixelShader(); + bool CompilePipeline(); + + size_t CalculateUniformsSize() const; + void FillUniformBuffer(const MathUtil::Rectangle<int>& src, const AbstractTexture* src_tex, + int src_layer); + // Timer for determining our time value Common::Timer m_timer; + PostProcessingConfiguration m_config; - PostProcessingShaderConfiguration m_config; + std::unique_ptr<AbstractShader> m_vertex_shader; + std::unique_ptr<AbstractShader> m_pixel_shader; + std::unique_ptr<AbstractPipeline> m_pipeline; + AbstractTextureFormat m_framebuffer_format = AbstractTextureFormat::Undefined; + std::vector<u8> m_uniform_staging_buffer; }; +} // namespace VideoCommon diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index 5a5b1c5aba..132ce4bd6c 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -50,14 +50,15 @@ #include "VideoCommon/AbstractFramebuffer.h" #include "VideoCommon/AbstractStagingTexture.h" #include "VideoCommon/AbstractTexture.h" +#include "VideoCommon/BPFunctions.h" #include "VideoCommon/BPMemory.h" #include "VideoCommon/CPMemory.h" #include "VideoCommon/CommandProcessor.h" -#include "VideoCommon/Debugger.h" #include "VideoCommon/FPSCounter.h" -#include "VideoCommon/FramebufferManagerBase.h" +#include "VideoCommon/FramebufferManager.h" #include "VideoCommon/ImageWrite.h" #include "VideoCommon/OnScreenDisplay.h" +#include "VideoCommon/PixelEngine.h" #include "VideoCommon/PixelShaderManager.h" #include "VideoCommon/PostProcessing.h" #include "VideoCommon/ShaderCache.h" @@ -68,12 +69,10 @@ #include "VideoCommon/VertexLoaderManager.h" #include "VideoCommon/VertexManagerBase.h" #include "VideoCommon/VertexShaderManager.h" +#include "VideoCommon/VideoBackendBase.h" #include "VideoCommon/VideoConfig.h" #include "VideoCommon/XFMemory.h" -// TODO: Move these out of here. -int frameCount; - std::unique_ptr<Renderer> g_renderer; static float AspectToWidescreen(float aspect) @@ -97,7 +96,14 @@ Renderer::~Renderer() = default; bool Renderer::Initialize() { - return InitializeImGui(); + if (!InitializeImGui()) + return false; + + m_post_processor = std::make_unique<VideoCommon::PostProcessing>(); + if (!m_post_processor->Initialize(m_backbuffer_format)) + return false; + + return true; } void Renderer::Shutdown() @@ -106,6 +112,142 @@ void Renderer::Shutdown() // can require additional graphics sub-systems so it needs to be done first ShutdownFrameDumping(); ShutdownImGui(); + m_post_processor.reset(); +} + +void Renderer::BeginUtilityDrawing() +{ + g_vertex_manager->Flush(); +} + +void Renderer::EndUtilityDrawing() +{ + // Reset framebuffer/scissor/viewport. Pipeline will be reset at next draw. + g_framebuffer_manager->BindEFBFramebuffer(); + BPFunctions::SetScissor(); + BPFunctions::SetViewport(); +} + +void Renderer::SetFramebuffer(AbstractFramebuffer* framebuffer) +{ + m_current_framebuffer = framebuffer; +} + +void Renderer::SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer) +{ + m_current_framebuffer = framebuffer; +} + +void Renderer::SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, + const ClearColor& color_value, float depth_value) +{ + m_current_framebuffer = framebuffer; +} + +std::unique_ptr<AbstractShader> Renderer::CreateShaderFromSource(ShaderStage stage, + const std::string& source) +{ + return CreateShaderFromSource(stage, source.c_str(), source.size()); +} + +void Renderer::ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable, + u32 color, u32 z) +{ + g_framebuffer_manager->ClearEFB(rc, colorEnable, alphaEnable, zEnable, color, z); +} + +void Renderer::ReinterpretPixelData(EFBReinterpretType convtype) +{ + g_framebuffer_manager->ReinterpretPixelData(convtype); +} + +u32 Renderer::AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data) +{ + if (type == EFBAccessType::PeekColor) + { + u32 color = g_framebuffer_manager->PeekEFBColor(x, y); + + // a little-endian value is expected to be returned + color = ((color & 0xFF00FF00) | ((color >> 16) & 0xFF) | ((color << 16) & 0xFF0000)); + + // check what to do with the alpha channel (GX_PokeAlphaRead) + PixelEngine::UPEAlphaReadReg alpha_read_mode = PixelEngine::GetAlphaReadMode(); + + if (bpmem.zcontrol.pixel_format == PEControl::RGBA6_Z24) + { + color = RGBA8ToRGBA6ToRGBA8(color); + } + else if (bpmem.zcontrol.pixel_format == PEControl::RGB565_Z16) + { + color = RGBA8ToRGB565ToRGBA8(color); + } + if (bpmem.zcontrol.pixel_format != PEControl::RGBA6_Z24) + { + color |= 0xFF000000; + } + + if (alpha_read_mode.ReadMode == 2) + { + return color; // GX_READ_NONE + } + else if (alpha_read_mode.ReadMode == 1) + { + return color | 0xFF000000; // GX_READ_FF + } + else /*if(alpha_read_mode.ReadMode == 0)*/ + { + return color & 0x00FFFFFF; // GX_READ_00 + } + } + else // if (type == EFBAccessType::PeekZ) + { + // Depth buffer is inverted for improved precision near far plane + float depth = g_framebuffer_manager->PeekEFBDepth(x, y); + if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange) + depth = 1.0f - depth; + + u32 ret = 0; + if (bpmem.zcontrol.pixel_format == PEControl::RGB565_Z16) + { + // if Z is in 16 bit format you must return a 16 bit integer + ret = MathUtil::Clamp<u32>(static_cast<u32>(depth * 65536.0f), 0, 0xFFFF); + } + else + { + ret = MathUtil::Clamp<u32>(static_cast<u32>(depth * 16777216.0f), 0, 0xFFFFFF); + } + + return ret; + } +} + +void Renderer::PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num_points) +{ + if (type == EFBAccessType::PokeColor) + { + for (size_t i = 0; i < num_points; i++) + { + // Convert to expected format (BGRA->RGBA) + // TODO: Check alpha, depending on mode? + const EfbPokeData& point = points[i]; + u32 color = ((point.data & 0xFF00FF00) | ((point.data >> 16) & 0xFF) | + ((point.data << 16) & 0xFF0000)); + g_framebuffer_manager->PokeEFBColor(point.x, point.y, color); + } + } + else // if (type == EFBAccessType::PokeZ) + { + for (size_t i = 0; i < num_points; i++) + { + // Convert to floating-point depth. + const EfbPokeData& point = points[i]; + float depth = float(point.data & 0xFFFFFF) / 16777216.0f; + if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange) + depth = 1.0f - depth; + + g_framebuffer_manager->PokeEFBDepth(point.x, point.y, depth); + } + } } void Renderer::RenderToXFB(u32 xfbAddr, const EFBRectangle& sourceRc, u32 fbStride, u32 fbHeight, @@ -169,6 +311,8 @@ bool Renderer::CalculateTargetSize() int new_efb_width = 0; int new_efb_height = 0; std::tie(new_efb_width, new_efb_height) = CalculateTargetScale(EFB_WIDTH, EFB_HEIGHT); + new_efb_width = std::max(new_efb_width, 1); + new_efb_height = std::max(new_efb_height, 1); if (new_efb_width != m_target_width || new_efb_height != m_target_height) { @@ -251,6 +395,11 @@ void Renderer::CheckForConfigChanges() // Update texture cache settings with any changed options. g_texture_cache->OnConfigChanged(g_ActiveConfig); + // Check for post-processing shader changes. Done up here as it doesn't affect anything outside + // the post-processor. Note that options are applied every frame, so no need to check those. + if (m_post_processor->GetConfig()->GetShader() != g_ActiveConfig.sPostProcessingShader) + m_post_processor->RecompileShader(); + // Determine which (if any) settings have changed. ShaderHostConfig new_host_config = ShaderHostConfig::GetCurrent(); u32 changed_bits = 0; @@ -278,13 +427,30 @@ void Renderer::CheckForConfigChanges() // Notify the backend of the changes, if any. OnConfigChanged(changed_bits); + // Framebuffer changed? + if (changed_bits & (CONFIG_CHANGE_BIT_MULTISAMPLES | CONFIG_CHANGE_BIT_STEREO_MODE | + CONFIG_CHANGE_BIT_TARGET_SIZE)) + { + g_framebuffer_manager->RecreateEFBFramebuffer(); + } + // Reload shaders if host config has changed. if (changed_bits & (CONFIG_CHANGE_BIT_HOST_CONFIG | CONFIG_CHANGE_BIT_MULTISAMPLES)) { OSD::AddMessage("Video config changed, reloading shaders.", OSD::Duration::NORMAL); + WaitForGPUIdle(); SetPipeline(nullptr); g_vertex_manager->InvalidatePipelineObject(); - g_shader_cache->SetHostConfig(new_host_config, g_ActiveConfig.iMultisamples); + g_shader_cache->SetHostConfig(new_host_config); + g_shader_cache->Reload(); + g_framebuffer_manager->RecompileShaders(); + } + + // Viewport and scissor rect have to be reset since they will be scaled differently. + if (changed_bits & CONFIG_CHANGE_BIT_TARGET_SIZE) + { + BPFunctions::SetViewport(); + BPFunctions::SetScissor(); } } @@ -389,6 +555,86 @@ void Renderer::ResizeSurface() m_surface_resized.Set(); } +void Renderer::SetViewportAndScissor(const MathUtil::Rectangle<int>& rect, float min_depth, + float max_depth) +{ + SetViewport(static_cast<float>(rect.left), static_cast<float>(rect.top), + static_cast<float>(rect.GetWidth()), static_cast<float>(rect.GetHeight()), min_depth, + max_depth); + SetScissorRect(rect); +} + +void Renderer::ScaleTexture(AbstractFramebuffer* dst_framebuffer, + const MathUtil::Rectangle<int>& dst_rect, + const AbstractTexture* src_texture, + const MathUtil::Rectangle<int>& src_rect) +{ + ASSERT(dst_framebuffer->GetColorFormat() == AbstractTextureFormat::RGBA8); + + BeginUtilityDrawing(); + + // The shader needs to know the source rectangle. + const auto converted_src_rect = g_renderer->ConvertFramebufferRectangle( + src_rect, src_texture->GetWidth(), src_texture->GetHeight()); + const float rcp_src_width = 1.0f / src_texture->GetWidth(); + const float rcp_src_height = 1.0f / src_texture->GetHeight(); + const std::array<float, 4> uniforms = {{converted_src_rect.left * rcp_src_width, + converted_src_rect.top * rcp_src_height, + converted_src_rect.GetWidth() * rcp_src_width, + converted_src_rect.GetHeight() * rcp_src_height}}; + g_vertex_manager->UploadUtilityUniforms(&uniforms, sizeof(uniforms)); + + // Discard if we're overwriting the whole thing. + if (static_cast<u32>(dst_rect.GetWidth()) == dst_framebuffer->GetWidth() && + static_cast<u32>(dst_rect.GetHeight()) == dst_framebuffer->GetHeight()) + { + SetAndDiscardFramebuffer(dst_framebuffer); + } + else + { + SetFramebuffer(dst_framebuffer); + } + + SetViewportAndScissor(ConvertFramebufferRectangle(dst_rect, dst_framebuffer)); + SetPipeline(dst_framebuffer->GetLayers() > 1 ? g_shader_cache->GetRGBA8StereoCopyPipeline() : + g_shader_cache->GetRGBA8CopyPipeline()); + SetTexture(0, src_texture); + SetSamplerState(0, RenderState::GetLinearSamplerState()); + Draw(0, 3); + EndUtilityDrawing(); + if (dst_framebuffer->GetColorAttachment()) + dst_framebuffer->GetColorAttachment()->FinishedRendering(); +} + +MathUtil::Rectangle<int> +Renderer::ConvertFramebufferRectangle(const MathUtil::Rectangle<int>& rect, + const AbstractFramebuffer* framebuffer) +{ + return ConvertFramebufferRectangle(rect, framebuffer->GetWidth(), framebuffer->GetHeight()); +} + +MathUtil::Rectangle<int> Renderer::ConvertFramebufferRectangle(const MathUtil::Rectangle<int>& rect, + u32 fb_width, u32 fb_height) +{ + MathUtil::Rectangle<int> ret = rect; + if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin) + { + ret.top = fb_height - rect.bottom; + ret.bottom = fb_height - rect.top; + } + return ret; +} + +TargetRectangle Renderer::ConvertEFBRectangle(const EFBRectangle& rc) +{ + TargetRectangle result; + result.left = EFBToScaledX(rc.left); + result.top = EFBToScaledY(rc.top); + result.right = EFBToScaledX(rc.right); + result.bottom = EFBToScaledY(rc.bottom); + return result; +} + std::tuple<float, float> Renderer::ScaleToDisplayAspectRatio(const int width, const int height) const { @@ -700,7 +946,7 @@ bool Renderer::InitializeImGui() vdecl.texcoords[0] = {VAR_FLOAT, 2, offsetof(ImDrawVert, uv), true, false}; vdecl.colors[0] = {VAR_UNSIGNED_BYTE, 4, offsetof(ImDrawVert, col), true, false}; vdecl.stride = sizeof(ImDrawVert); - m_imgui_vertex_format = g_vertex_manager->CreateNativeVertexFormat(vdecl); + m_imgui_vertex_format = CreateNativeVertexFormat(vdecl); if (!m_imgui_vertex_format) { PanicAlert("Failed to create imgui vertex format"); @@ -723,10 +969,10 @@ bool Renderer::InitializeImGui() pconfig.vertex_format = m_imgui_vertex_format.get(); pconfig.vertex_shader = vertex_shader.get(); pconfig.pixel_shader = pixel_shader.get(); - pconfig.rasterization_state.hex = RenderState::GetNoCullRasterizationState().hex; - pconfig.rasterization_state.primitive = PrimitiveType::Triangles; - pconfig.depth_state.hex = RenderState::GetNoDepthTestingDepthStencilState().hex; - pconfig.blending_state.hex = RenderState::GetNoBlendingBlendState().hex; + pconfig.rasterization_state = + RenderState::GetCullBackFaceRasterizationState(PrimitiveType::Triangles); + pconfig.depth_state = RenderState::GetNoDepthTestingDepthState(); + pconfig.blending_state = RenderState::GetNoBlendingBlendState(); pconfig.blending_state.blendenable = true; pconfig.blending_state.srcfactor = BlendMode::SRCALPHA; pconfig.blending_state.dstfactor = BlendMode::INVSRCALPHA; @@ -752,7 +998,7 @@ bool Renderer::InitializeImGui() io.Fonts->GetTexDataAsRGBA32(&font_tex_pixels, &font_tex_width, &font_tex_height); TextureConfig font_tex_config(font_tex_width, font_tex_height, 1, 1, 1, - AbstractTextureFormat::RGBA8, false); + AbstractTextureFormat::RGBA8, 0); std::unique_ptr<AbstractTexture> font_tex = CreateTexture(font_tex_config); if (!font_tex) { @@ -799,10 +1045,8 @@ void Renderer::BeginImGuiFrame() ImGui::NewFrame(); } -void Renderer::RenderImGui() +void Renderer::DrawImGui() { - ImGui::Render(); - ImDrawData* draw_data = ImGui::GetDrawData(); if (!draw_data) return; @@ -842,9 +1086,11 @@ void Renderer::RenderImGui() continue; } - SetScissorRect(MathUtil::Rectangle<int>( - static_cast<int>(cmd.ClipRect.x), static_cast<int>(cmd.ClipRect.y), - static_cast<int>(cmd.ClipRect.z), static_cast<int>(cmd.ClipRect.w))); + SetScissorRect(ConvertFramebufferRectangle( + MathUtil::Rectangle<int>( + static_cast<int>(cmd.ClipRect.x), static_cast<int>(cmd.ClipRect.y), + static_cast<int>(cmd.ClipRect.z), static_cast<int>(cmd.ClipRect.w)), + m_current_framebuffer)); SetTexture(0, reinterpret_cast<const AbstractTexture*>(cmd.TextureId)); DrawIndexed(base_index, cmd.ElemCount, base_vertex); base_index += cmd.ElemCount; @@ -859,7 +1105,10 @@ std::unique_lock<std::mutex> Renderer::GetImGuiLock() void Renderer::BeginUIFrame() { - ResetAPIState(); + if (IsHeadless()) + return; + + BeginUtilityDrawing(); BindBackbuffer({0.0f, 0.0f, 0.0f, 1.0f}); } @@ -867,16 +1116,19 @@ void Renderer::EndUIFrame() { { auto lock = GetImGuiLock(); - RenderImGui(); + ImGui::Render(); } + if (!IsHeadless()) { + DrawImGui(); + std::lock_guard<std::mutex> guard(m_swap_mutex); PresentBackbuffer(); + EndUtilityDrawing(); } BeginImGuiFrame(); - RestoreAPIState(); } void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const EFBRectangle& rc, @@ -946,33 +1198,35 @@ 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. g_vertex_manager->Flush(); - // Render the XFB to the screen. - ResetAPIState(); - BindBackbuffer({0.0f, 0.0f, 0.0f, 1.0f}); - UpdateDrawRectangle(); - RenderXFBToScreen(xfb_entry->texture.get(), xfb_rect); - - // Hold the imgui lock while we're presenting. - // It's only to prevent races on inputs anyway, at this point. + // Render any UI elements to the draw list. { auto lock = GetImGuiLock(); DrawDebugText(); OSD::DrawMessages(); - - RenderImGui(); + ImGui::Render(); } - // Present to the window system. + // Render the XFB to the screen. + BeginUtilityDrawing(); + if (!IsHeadless()) { - std::lock_guard<std::mutex> guard(m_swap_mutex); - PresentBackbuffer(); + BindBackbuffer({{0.0f, 0.0f, 0.0f, 1.0f}}); + UpdateDrawRectangle(); + RenderXFBToScreen(xfb_entry->texture.get(), xfb_rect); + DrawImGui(); + + // Present to the window system. + { + std::lock_guard<std::mutex> guard(m_swap_mutex); + PresentBackbuffer(); + } + + // Update the window size based on the frame that was just rendered. + // Due to depending on guest state, we need to call this every frame. + SetWindowSize(texture_config.width, texture_config.height); } - // Update the window size based on the frame that was just rendered. - // Due to depending on guest state, we need to call this every frame. - SetWindowSize(texture_config.width, texture_config.height); - m_fps_counter.Update(); DolphinAnalytics::PerformanceSample perf_sample; @@ -984,12 +1238,11 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const if (IsFrameDumping()) DumpCurrentFrame(); - frameCount++; - GFX_DEBUGGER_PAUSE_AT(NEXT_FRAME, true); - // Begin new frame + m_frame_count++; stats.ResetFrame(); g_shader_cache->RetrieveAsyncShaders(); + g_vertex_manager->OnEndFrame(); BeginImGuiFrame(); // We invalidate the pipeline object at the start of the frame. @@ -1003,13 +1256,13 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const g_texture_cache->FlushEFBCopies(); // Remove stale EFB/XFB copies. - g_texture_cache->Cleanup(frameCount); + g_texture_cache->Cleanup(m_frame_count); // Handle any config changes, this gets propogated to the backend. CheckForConfigChanges(); g_Config.iSaveTargetId = 0; - RestoreAPIState(); + EndUtilityDrawing(); Core::Callback_VideoCopiedToXFB(true); } @@ -1028,6 +1281,24 @@ void Renderer::Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const } } +void Renderer::RenderXFBToScreen(const AbstractTexture* texture, const EFBRectangle& rc) +{ + const auto target_rc = GetTargetRectangle(); + if (g_ActiveConfig.stereo_mode == StereoMode::SBS || + g_ActiveConfig.stereo_mode == StereoMode::TAB) + { + TargetRectangle left_rc, right_rc; + std::tie(left_rc, right_rc) = ConvertStereoRectangle(target_rc); + + m_post_processor->BlitFromTexture(left_rc, rc, texture, 0); + m_post_processor->BlitFromTexture(right_rc, rc, texture, 1); + } + else + { + m_post_processor->BlitFromTexture(target_rc, rc, texture, 0); + } +} + bool Renderer::IsFrameDumping() { if (m_screenshot_request.IsSet()) @@ -1041,15 +1312,6 @@ bool Renderer::IsFrameDumping() void Renderer::DumpCurrentFrame() { - // Scale/render to frame dump texture. - RenderFrameDump(); - - // Queue a readback for the next frame. - QueueFrameDumpReadback(); -} - -void Renderer::RenderFrameDump() -{ int target_width, target_height; if (!g_ActiveConfig.bInternalResolutionFrameDumps && !IsHeadless()) { @@ -1063,50 +1325,76 @@ void Renderer::RenderFrameDump() m_last_xfb_texture->GetConfig().width, m_last_xfb_texture->GetConfig().height); } - // Ensure framebuffer exists (we lazily allocate it in case frame dumping isn't used). - // Or, resize texture if it isn't large enough to accommodate the current frame. - if (!m_frame_dump_render_texture || - m_frame_dump_render_texture->GetConfig().width != static_cast<u32>(target_width) || - m_frame_dump_render_texture->GetConfig().height != static_cast<u32>(target_height)) + // We only need to render a copy if we need to stretch/scale the XFB copy. + const AbstractTexture* source_tex = m_last_xfb_texture; + MathUtil::Rectangle<int> source_rect = m_last_xfb_region; + if (source_rect.GetWidth() != target_width || source_rect.GetHeight() != target_height) { - // Recreate texture objects. Release before creating so we don't temporarily use twice the RAM. - TextureConfig config(target_width, target_height, 1, 1, 1, AbstractTextureFormat::RGBA8, true); - m_frame_dump_render_texture.reset(); - m_frame_dump_render_texture = CreateTexture(config); - ASSERT(m_frame_dump_render_texture); + if (!CheckFrameDumpRenderTexture(target_width, target_height)) + return; + + source_tex = m_frame_dump_render_texture.get(); + source_rect = MathUtil::Rectangle<int>(0, 0, target_width, target_height); + ScaleTexture(m_frame_dump_render_framebuffer.get(), source_rect, m_last_xfb_texture, + m_last_xfb_region); } - // Scaling is likely to occur here, but if possible, do a bit-for-bit copy. - if (m_last_xfb_region.GetWidth() != target_width || - m_last_xfb_region.GetHeight() != target_height) + // Index 0 was just sent to AVI dump. Swap with the second texture. + if (m_frame_dump_readback_textures[0]) + std::swap(m_frame_dump_readback_textures[0], m_frame_dump_readback_textures[1]); + + if (!CheckFrameDumpReadbackTexture(target_width, target_height)) + return; + + const auto converted_region = + ConvertFramebufferRectangle(source_rect, source_tex->GetWidth(), source_tex->GetHeight()); + m_frame_dump_readback_textures[0]->CopyFromTexture( + source_tex, converted_region, 0, 0, + MathUtil::Rectangle<int>(0, 0, target_width, target_height)); + m_last_frame_state = AVIDump::FetchState(m_last_xfb_ticks); + m_last_frame_exported = true; +} + +bool Renderer::CheckFrameDumpRenderTexture(u32 target_width, u32 target_height) +{ + // Ensure framebuffer exists (we lazily allocate it in case frame dumping isn't used). + // Or, resize texture if it isn't large enough to accommodate the current frame. + if (m_frame_dump_render_texture && m_frame_dump_render_texture->GetWidth() == target_width && + m_frame_dump_render_texture->GetHeight() == target_height) { - m_frame_dump_render_texture->ScaleRectangleFromTexture( - m_last_xfb_texture, m_last_xfb_region, EFBRectangle{0, 0, target_width, target_height}); + return true; } - else + + // Recreate texture, but release before creating so we don't temporarily use twice the RAM. + m_frame_dump_render_framebuffer.reset(); + m_frame_dump_render_texture.reset(); + m_frame_dump_render_texture = + CreateTexture(TextureConfig(target_width, target_height, 1, 1, 1, + AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget)); + if (!m_frame_dump_render_texture) { - m_frame_dump_render_texture->CopyRectangleFromTexture( - m_last_xfb_texture, m_last_xfb_region, 0, 0, - EFBRectangle{0, 0, target_width, target_height}, 0, 0); + PanicAlert("Failed to allocate frame dump render texture"); + return false; } + m_frame_dump_render_framebuffer = CreateFramebuffer(m_frame_dump_render_texture.get(), nullptr); + ASSERT(m_frame_dump_render_framebuffer); + return true; } -void Renderer::QueueFrameDumpReadback() +bool Renderer::CheckFrameDumpReadbackTexture(u32 target_width, u32 target_height) { - // Index 0 was just sent to AVI dump. Swap with the second texture. - if (m_frame_dump_readback_textures[0]) - std::swap(m_frame_dump_readback_textures[0], m_frame_dump_readback_textures[1]); - std::unique_ptr<AbstractStagingTexture>& rbtex = m_frame_dump_readback_textures[0]; - if (!rbtex || rbtex->GetConfig() != m_frame_dump_render_texture->GetConfig()) - { - rbtex = CreateStagingTexture(StagingTextureType::Readback, - m_frame_dump_render_texture->GetConfig()); - } + if (rbtex && rbtex->GetWidth() == target_width && rbtex->GetHeight() == target_height) + return true; - m_last_frame_state = AVIDump::FetchState(m_last_xfb_ticks); - m_last_frame_exported = true; - rbtex->CopyFromTexture(m_frame_dump_render_texture.get(), 0, 0); + rbtex.reset(); + rbtex = CreateStagingTexture( + StagingTextureType::Readback, + TextureConfig(target_width, target_height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0)); + if (!rbtex) + return false; + + return true; } void Renderer::FlushFrameDump() @@ -1151,6 +1439,7 @@ void Renderer::ShutdownFrameDumping() m_frame_dump_start.Set(); if (m_frame_dump_thread.joinable()) m_frame_dump_thread.join(); + m_frame_dump_render_framebuffer.reset(); m_frame_dump_render_texture.reset(); for (auto& tex : m_frame_dump_readback_textures) tex.reset(); diff --git a/Source/Core/VideoCommon/RenderBase.h b/Source/Core/VideoCommon/RenderBase.h index 45a82c1968..deaaa89aa9 100644 --- a/Source/Core/VideoCommon/RenderBase.h +++ b/Source/Core/VideoCommon/RenderBase.h @@ -41,22 +41,26 @@ class AbstractShader; class AbstractTexture; class AbstractStagingTexture; class NativeVertexFormat; -class PostProcessingShaderImplementation; struct TextureConfig; struct ComputePipelineConfig; struct AbstractPipelineConfig; +struct PortableVertexDeclaration; enum class ShaderStage; enum class EFBAccessType; +enum class EFBReinterpretType; enum class StagingTextureType; +namespace VideoCommon +{ +class PostProcessing; +} + struct EfbPokeData { u16 x, y; u32 data; }; -extern int frameCount; - // Renderer really isn't a very good name for this class - it's more like "Misc". // The long term goal is to get rid of this class and replace it with others that make // more sense. @@ -78,37 +82,38 @@ public: virtual void SetScissorRect(const MathUtil::Rectangle<int>& rc) {} virtual void SetTexture(u32 index, const AbstractTexture* texture) {} virtual void SetSamplerState(u32 index, const SamplerState& state) {} + virtual void SetComputeImageTexture(AbstractTexture* texture, bool read, bool write) {} virtual void UnbindTexture(const AbstractTexture* texture) {} - virtual void SetInterlacingMode() {} virtual void SetViewport(float x, float y, float width, float height, float near_depth, float far_depth) { } virtual void SetFullscreen(bool enable_fullscreen) {} virtual bool IsFullscreen() const { return false; } - virtual void ApplyState() {} - virtual void RestoreState() {} - virtual void ResetAPIState() {} - virtual void RestoreAPIState() {} + virtual void BeginUtilityDrawing(); + virtual void EndUtilityDrawing(); virtual std::unique_ptr<AbstractTexture> CreateTexture(const TextureConfig& config) = 0; virtual std::unique_ptr<AbstractStagingTexture> CreateStagingTexture(StagingTextureType type, const TextureConfig& config) = 0; virtual std::unique_ptr<AbstractFramebuffer> - CreateFramebuffer(const AbstractTexture* color_attachment, - const AbstractTexture* depth_attachment) = 0; + CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment) = 0; // Framebuffer operations. - virtual void SetFramebuffer(const AbstractFramebuffer* framebuffer) {} - virtual void SetAndDiscardFramebuffer(const AbstractFramebuffer* framebuffer) {} - virtual void SetAndClearFramebuffer(const AbstractFramebuffer* framebuffer, - const ClearColor& color_value = {}, float depth_value = 0.0f) - { - } + virtual void SetFramebuffer(AbstractFramebuffer* framebuffer); + virtual void SetAndDiscardFramebuffer(AbstractFramebuffer* framebuffer); + virtual void SetAndClearFramebuffer(AbstractFramebuffer* framebuffer, + const ClearColor& color_value = {}, float depth_value = 0.0f); // Drawing with currently-bound pipeline state. virtual void Draw(u32 base_vertex, u32 num_vertices) {} virtual void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) {} + // Dispatching compute shaders with currently-bound state. + virtual void DispatchComputeShader(const AbstractShader* shader, u32 groups_x, u32 groups_y, + u32 groups_z) + { + } + // 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. @@ -122,12 +127,15 @@ public: CreateShaderFromSource(ShaderStage stage, const char* source, size_t length) = 0; virtual std::unique_ptr<AbstractShader> CreateShaderFromBinary(ShaderStage stage, const void* data, size_t length) = 0; + virtual std::unique_ptr<NativeVertexFormat> + CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) = 0; virtual std::unique_ptr<AbstractPipeline> CreatePipeline(const AbstractPipelineConfig& config) = 0; + std::unique_ptr<AbstractShader> CreateShaderFromSource(ShaderStage stage, + const std::string& source); + + AbstractFramebuffer* GetCurrentFramebuffer() const { return m_current_framebuffer; } - const AbstractFramebuffer* GetCurrentFramebuffer() const { return m_current_framebuffer; } - u32 GetCurrentFramebufferWidth() const { return m_current_framebuffer_width; } - u32 GetCurrentFramebufferHeight() const { return m_current_framebuffer_height; } // Ideal internal resolution - multiple of the native EFB resolution int GetTargetWidth() const { return m_target_width; } int GetTargetHeight() const { return m_target_height; } @@ -137,10 +145,27 @@ public: float GetBackbufferScale() const { return m_backbuffer_scale; } void SetWindowSize(int width, int height); - // EFB coordinate conversion functions + // Sets viewport and scissor to the specified rectangle. rect is assumed to be in framebuffer + // coordinates, i.e. lower-left origin in OpenGL. + void SetViewportAndScissor(const MathUtil::Rectangle<int>& rect, float min_depth = 0.0f, + float max_depth = 1.0f); + + // Scales a GPU texture using a copy shader. + virtual void ScaleTexture(AbstractFramebuffer* dst_framebuffer, + const MathUtil::Rectangle<int>& dst_rect, + const AbstractTexture* src_texture, + const MathUtil::Rectangle<int>& src_rect); + + // Converts an upper-left to lower-left if required by the backend, optionally + // clamping to the framebuffer size. + MathUtil::Rectangle<int> ConvertFramebufferRectangle(const MathUtil::Rectangle<int>& rect, + u32 fb_width, u32 fb_height); + MathUtil::Rectangle<int> ConvertFramebufferRectangle(const MathUtil::Rectangle<int>& rect, + const AbstractFramebuffer* framebuffer); + // EFB coordinate conversion functions // Use this to convert a whole native EFB rect to backbuffer coordinates - virtual TargetRectangle ConvertEFBRectangle(const EFBRectangle& rc) = 0; + TargetRectangle ConvertEFBRectangle(const EFBRectangle& rc); const TargetRectangle& GetTargetRectangle() const { return m_target_rectangle; } float CalculateDrawAspectRatio() const; @@ -170,18 +195,20 @@ public: bool InitializeImGui(); virtual void ClearScreen(const EFBRectangle& rc, bool colorEnable, bool alphaEnable, bool zEnable, - u32 color, u32 z) = 0; - virtual void ReinterpretPixelData(unsigned int convtype) = 0; + u32 color, u32 z); + virtual void ReinterpretPixelData(EFBReinterpretType convtype); void RenderToXFB(u32 xfbAddr, const EFBRectangle& sourceRc, u32 fbStride, u32 fbHeight, float Gamma = 1.0f); - virtual u32 AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data) = 0; - virtual void PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num_points) = 0; + virtual u32 AccessEFB(EFBAccessType type, u32 x, u32 y, u32 poke_data); + virtual void PokeEFB(EFBAccessType type, const EfbPokeData* points, size_t num_points); virtual u16 BBoxRead(int index) = 0; virtual void BBoxWrite(int index, u16 value) = 0; + virtual void BBoxFlush() {} virtual void Flush() {} + virtual void WaitForGPUIdle() {} // Finish up the current frame, print some stats void Swap(u32 xfbAddr, u32 fbWidth, u32 fbStride, u32 fbHeight, const EFBRectangle& rc, @@ -189,14 +216,14 @@ public: // 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) {} + 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; } void StorePixelFormat(PEControl::PixelFormat new_format) { m_prev_efb_format = new_format; } - PostProcessingShaderImplementation* GetPostProcessor() const { return m_post_processor.get(); } + VideoCommon::PostProcessing* GetPostProcessor() const { return m_post_processor.get(); } // Final surface changing // This is called when the surface is resized (WX) or the window changes (Android). void ChangeSurface(void* new_surface_handle); @@ -246,12 +273,10 @@ protected: // Renders ImGui windows to the currently-bound framebuffer. // Should be called with the ImGui lock held. - void RenderImGui(); + void DrawImGui(); - // TODO: Remove the width/height parameters once we make the EFB an abstract framebuffer. - const AbstractFramebuffer* m_current_framebuffer = nullptr; - u32 m_current_framebuffer_width = 1; - u32 m_current_framebuffer_height = 1; + AbstractFramebuffer* m_current_framebuffer = nullptr; + const AbstractPipeline* m_current_pipeline = nullptr; Common::Flag m_screenshot_request; Common::Event m_screenshot_completed; @@ -260,8 +285,8 @@ protected: bool m_aspect_wide = false; // The framebuffer size - int m_target_width = 0; - int m_target_height = 0; + int m_target_width = 1; + int m_target_height = 1; // Backbuffer (window) size and render area int m_backbuffer_width = 0; @@ -269,10 +294,11 @@ protected: float m_backbuffer_scale = 1.0f; AbstractTextureFormat m_backbuffer_format = AbstractTextureFormat::Undefined; TargetRectangle m_target_rectangle = {}; + int m_frame_count = 0; FPSCounter m_fps_counter; - std::unique_ptr<PostProcessingShaderImplementation> m_post_processor; + std::unique_ptr<VideoCommon::PostProcessing> m_post_processor; void* m_new_surface_handle = nullptr; Common::Flag m_surface_changed; @@ -315,6 +341,7 @@ private: // Texture used for screenshot/frame dumping std::unique_ptr<AbstractTexture> m_frame_dump_render_texture; + std::unique_ptr<AbstractFramebuffer> m_frame_dump_render_framebuffer; std::array<std::unique_ptr<AbstractStagingTexture>, 2> m_frame_dump_readback_textures; AVIDump::Frame m_last_frame_state; bool m_last_frame_exported = false; @@ -340,14 +367,14 @@ private: bool IsFrameDumping(); - // Asynchronously encodes the current staging texture to the frame dump. - void DumpCurrentFrame(); + // Checks that the frame dump render texture exists and is the correct size. + bool CheckFrameDumpRenderTexture(u32 target_width, u32 target_height); - // Fills the frame dump render texture with the current XFB texture. - void RenderFrameDump(); + // Checks that the frame dump readback texture exists and is the correct size. + bool CheckFrameDumpReadbackTexture(u32 target_width, u32 target_height); - // Queues the current frame for readback, which will be written to AVI next frame. - void QueueFrameDumpReadback(); + // Fills the frame dump staging texture with the current XFB texture. + void DumpCurrentFrame(); // Asynchronously encodes the specified pointer of frame data to the frame dump. void DumpFrameData(const u8* data, int w, int h, int stride, const AVIDump::Frame& state); diff --git a/Source/Core/VideoCommon/RenderState.cpp b/Source/Core/VideoCommon/RenderState.cpp index b61b031b04..04d8804f0c 100644 --- a/Source/Core/VideoCommon/RenderState.cpp +++ b/Source/Core/VideoCommon/RenderState.cpp @@ -6,6 +6,7 @@ #include <algorithm> #include <array> #include "VideoCommon/SamplerCommon.h" +#include "VideoCommon/TextureConfig.h" void RasterizationState::Generate(const BPMemory& bp, PrimitiveType primitive_type) { @@ -23,6 +24,12 @@ RasterizationState& RasterizationState::operator=(const RasterizationState& rhs) return *this; } +FramebufferState& FramebufferState::operator=(const FramebufferState& rhs) +{ + hex = rhs.hex; + return *this; +} + void DepthState::Generate(const BPMemory& bp) { testenable = bp.zmode.testenable.Value(); @@ -206,10 +213,19 @@ RasterizationState GetInvalidRasterizationState() return state; } -RasterizationState GetNoCullRasterizationState() +RasterizationState GetNoCullRasterizationState(PrimitiveType primitive) { RasterizationState state = {}; state.cullmode = GenMode::CULL_NONE; + state.primitive = primitive; + return state; +} + +RasterizationState GetCullBackFaceRasterizationState(PrimitiveType primitive) +{ + RasterizationState state = {}; + state.cullmode = GenMode::CULL_BACK; + state.primitive = primitive; return state; } @@ -220,7 +236,7 @@ DepthState GetInvalidDepthState() return state; } -DepthState GetNoDepthTestingDepthStencilState() +DepthState GetNoDepthTestingDepthState() { DepthState state = {}; state.testenable = false; @@ -229,6 +245,15 @@ DepthState GetNoDepthTestingDepthStencilState() return state; } +DepthState GetAlwaysWriteDepthState() +{ + DepthState state = {}; + state.testenable = true; + state.updateenable = true; + state.func = ZMode::ALWAYS; + return state; +} + BlendingState GetInvalidBlendingState() { BlendingState state; @@ -251,6 +276,21 @@ BlendingState GetNoBlendingBlendState() return state; } +BlendingState GetNoColorWriteBlendState() +{ + BlendingState state = {}; + state.usedualsrc = false; + state.blendenable = false; + state.srcfactor = BlendMode::ONE; + state.srcfactoralpha = BlendMode::ONE; + state.dstfactor = BlendMode::ZERO; + state.dstfactoralpha = BlendMode::ZERO; + state.logicopenable = false; + state.colorupdate = false; + state.alphaupdate = false; + return state; +} + SamplerState GetInvalidSamplerState() { SamplerState state; @@ -287,4 +327,20 @@ SamplerState GetLinearSamplerState() state.anisotropic_filtering = false; return state; } + +FramebufferState GetColorFramebufferState(AbstractTextureFormat format) +{ + FramebufferState state = {}; + state.color_texture_format = format; + state.depth_texture_format = AbstractTextureFormat::Undefined; + state.per_sample_shading = false; + state.samples = 1; + return state; +} + +FramebufferState GetRGBA8FramebufferState() +{ + return GetColorFramebufferState(AbstractTextureFormat::RGBA8); } + +} // namespace RenderState diff --git a/Source/Core/VideoCommon/RenderState.h b/Source/Core/VideoCommon/RenderState.h index 5cd0ba7470..dc0676ccc4 100644 --- a/Source/Core/VideoCommon/RenderState.h +++ b/Source/Core/VideoCommon/RenderState.h @@ -9,6 +9,8 @@ #include "VideoCommon/BPMemory.h" #include "VideoCommon/BPStructs.h" +enum class AbstractTextureFormat : u32; + enum class PrimitiveType : u32 { Points, @@ -32,6 +34,20 @@ union RasterizationState u32 hex; }; +union FramebufferState +{ + BitField<0, 8, AbstractTextureFormat> color_texture_format; + BitField<8, 8, AbstractTextureFormat> depth_texture_format; + BitField<16, 8, u32> samples; + BitField<24, 1, u32> per_sample_shading; + + bool operator==(const FramebufferState& rhs) const { return hex == rhs.hex; } + bool operator!=(const FramebufferState& rhs) const { return hex != rhs.hex; } + FramebufferState& operator=(const FramebufferState& rhs); + + u32 hex; +}; + union DepthState { void Generate(const BPMemory& bp); @@ -114,12 +130,17 @@ union SamplerState namespace RenderState { RasterizationState GetInvalidRasterizationState(); -RasterizationState GetNoCullRasterizationState(); +RasterizationState GetNoCullRasterizationState(PrimitiveType primitive); +RasterizationState GetCullBackFaceRasterizationState(PrimitiveType primitive); DepthState GetInvalidDepthState(); -DepthState GetNoDepthTestingDepthStencilState(); +DepthState GetNoDepthTestingDepthState(); +DepthState GetAlwaysWriteDepthState(); BlendingState GetInvalidBlendingState(); BlendingState GetNoBlendingBlendState(); +BlendingState GetNoColorWriteBlendState(); SamplerState GetInvalidSamplerState(); SamplerState GetPointSamplerState(); SamplerState GetLinearSamplerState(); -} +FramebufferState GetColorFramebufferState(AbstractTextureFormat format); +FramebufferState GetRGBA8FramebufferState(); +} // namespace RenderState diff --git a/Source/Core/VideoCommon/ShaderCache.cpp b/Source/Core/VideoCommon/ShaderCache.cpp index 3b0c110f96..b42587a2d4 100644 --- a/Source/Core/VideoCommon/ShaderCache.cpp +++ b/Source/Core/VideoCommon/ShaderCache.cpp @@ -9,7 +9,8 @@ #include "Common/MsgHandler.h" #include "Core/ConfigManager.h" -#include "VideoCommon/FramebufferManagerBase.h" +#include "VideoCommon/FramebufferManager.h" +#include "VideoCommon/FramebufferShaderGen.h" #include "VideoCommon/RenderBase.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/VertexLoaderManager.h" @@ -22,17 +23,26 @@ std::unique_ptr<VideoCommon::ShaderCache> g_shader_cache; namespace VideoCommon { ShaderCache::ShaderCache() = default; -ShaderCache::~ShaderCache() = default; +ShaderCache::~ShaderCache() +{ + ClearShaderCaches(); + ClearPipelineCaches(); +} bool ShaderCache::Initialize() { m_api_type = g_ActiveConfig.backend_info.api_type; m_host_config = ShaderHostConfig::GetCurrent(); - m_efb_depth_format = FramebufferManagerBase::GetEFBDepthFormat(); - m_efb_multisamples = g_ActiveConfig.iMultisamples; - // Create the async compiler, and start the worker threads. + if (!CompileSharedPipelines()) + return false; + m_async_shader_compiler = g_renderer->CreateAsyncShaderCompiler(); + return true; +} + +void ShaderCache::InitializeShaderCache() +{ m_async_shader_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderPrecompilerThreads()); // Load shader and UID caches. @@ -53,17 +63,6 @@ bool ShaderCache::Initialize() // Switch to the runtime shader compiler thread configuration. m_async_shader_compiler->ResizeWorkerThreads(g_ActiveConfig.GetShaderCompilerThreads()); - return true; -} - -void ShaderCache::SetHostConfig(const ShaderHostConfig& host_config, u32 efb_multisamples) -{ - if (m_host_config.bits == host_config.bits && m_efb_multisamples == efb_multisamples) - return; - - m_host_config = host_config; - m_efb_multisamples = efb_multisamples; - Reload(); } void ShaderCache::Reload() @@ -99,8 +98,6 @@ void ShaderCache::Shutdown() // until everything has finished compiling. m_async_shader_compiler->StopWorkerThreads(); ClosePipelineUIDCache(); - ClearShaderCaches(); - ClearPipelineCaches(); } const AbstractPipeline* ShaderCache::GetPipelineForUid(const GXPipelineUid& uid) @@ -445,6 +442,11 @@ bool ShaderCache::NeedsGeometryShader(const GeometryShaderUid& uid) const return m_host_config.backend_geometry_shaders && !uid.GetUidData()->IsPassthrough(); } +bool ShaderCache::UseGeometryShaderForEFBCopies() const +{ + return m_host_config.backend_geometry_shaders && m_host_config.stereo; +} + AbstractPipelineConfig ShaderCache::GetGXPipelineConfig( const NativeVertexFormat* vertex_format, const AbstractShader* vertex_shader, const AbstractShader* geometry_shader, const AbstractShader* pixel_shader, @@ -460,10 +462,7 @@ AbstractPipelineConfig ShaderCache::GetGXPipelineConfig( config.rasterization_state = rasterization_state; config.depth_state = depth_state; config.blending_state = blending_state; - config.framebuffer_state.color_texture_format = AbstractTextureFormat::RGBA8; - config.framebuffer_state.depth_texture_format = m_efb_depth_format; - config.framebuffer_state.per_sample_shading = m_host_config.ssaa; - config.framebuffer_state.samples = m_efb_multisamples; + config.framebuffer_state = g_framebuffer_manager->GetEFBFramebufferState(); return config; } @@ -967,8 +966,9 @@ void ShaderCache::QueueUberShaderPipelines() config.vs_uid = vs_uid; config.gs_uid = gs_uid; config.ps_uid = ps_uid; - config.rasterization_state = RenderState::GetNoCullRasterizationState(); - config.depth_state = RenderState::GetNoDepthTestingDepthStencilState(); + config.rasterization_state = + RenderState::GetCullBackFaceRasterizationState(PrimitiveType::TriangleStrip); + config.depth_state = RenderState::GetNoDepthTestingDepthState(); config.blending_state = RenderState::GetNoBlendingBlendState(); auto iter = m_gx_uber_pipeline_cache.find(config); @@ -998,24 +998,172 @@ void ShaderCache::QueueUberShaderPipelines() }); } -std::string ShaderCache::GetUtilityShaderHeader() const +const AbstractPipeline* +ShaderCache::GetEFBCopyToVRAMPipeline(const TextureConversionShaderGen::TCShaderUid& uid) +{ + auto iter = m_efb_copy_to_vram_pipelines.find(uid); + if (iter != m_efb_copy_to_vram_pipelines.end()) + return iter->second.get(); + + auto shader_code = TextureConversionShaderGen::GeneratePixelShader(m_api_type, uid.GetUidData()); + auto shader = g_renderer->CreateShaderFromSource(ShaderStage::Pixel, shader_code.GetBuffer()); + if (!shader) + { + m_efb_copy_to_vram_pipelines.emplace(uid, nullptr); + return nullptr; + } + + AbstractPipelineConfig config = {}; + config.vertex_format = nullptr; + config.vertex_shader = m_efb_copy_vertex_shader.get(); + config.geometry_shader = + UseGeometryShaderForEFBCopies() ? m_texcoord_geometry_shader.get() : nullptr; + config.pixel_shader = shader.get(); + config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles); + config.depth_state = RenderState::GetNoDepthTestingDepthState(); + config.blending_state = RenderState::GetNoBlendingBlendState(); + config.framebuffer_state = RenderState::GetRGBA8FramebufferState(); + config.usage = AbstractPipelineUsage::Utility; + auto iiter = m_efb_copy_to_vram_pipelines.emplace(uid, g_renderer->CreatePipeline(config)); + return iiter.first->second.get(); +} + +const AbstractPipeline* ShaderCache::GetEFBCopyToRAMPipeline(const EFBCopyParams& uid) +{ + auto iter = m_efb_copy_to_ram_pipelines.find(uid); + if (iter != m_efb_copy_to_ram_pipelines.end()) + return iter->second.get(); + + auto shader_code = TextureConversionShaderTiled::GenerateEncodingShader(uid, m_api_type); + auto shader = + g_renderer->CreateShaderFromSource(ShaderStage::Pixel, shader_code, std::strlen(shader_code)); + if (!shader) + { + m_efb_copy_to_ram_pipelines.emplace(uid, nullptr); + return nullptr; + } + + AbstractPipelineConfig config = {}; + config.vertex_format = nullptr; + config.vertex_shader = m_screen_quad_vertex_shader.get(); + config.geometry_shader = + UseGeometryShaderForEFBCopies() ? m_texcoord_geometry_shader.get() : nullptr; + config.pixel_shader = shader.get(); + config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles); + config.depth_state = RenderState::GetNoDepthTestingDepthState(); + config.blending_state = RenderState::GetNoBlendingBlendState(); + config.framebuffer_state = RenderState::GetColorFramebufferState(AbstractTextureFormat::BGRA8); + config.usage = AbstractPipelineUsage::Utility; + auto iiter = m_efb_copy_to_ram_pipelines.emplace(uid, g_renderer->CreatePipeline(config)); + return iiter.first->second.get(); +} + +bool ShaderCache::CompileSharedPipelines() { - std::stringstream ss; + m_screen_quad_vertex_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Vertex, FramebufferShaderGen::GenerateScreenQuadVertexShader()); + m_texture_copy_vertex_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Vertex, FramebufferShaderGen::GenerateTextureCopyVertexShader()); + m_efb_copy_vertex_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Vertex, + TextureConversionShaderGen::GenerateVertexShader(m_api_type).GetBuffer()); + if (!m_screen_quad_vertex_shader || !m_texture_copy_vertex_shader || !m_efb_copy_vertex_shader) + return false; + + if (UseGeometryShaderForEFBCopies()) + { + m_texcoord_geometry_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Geometry, FramebufferShaderGen::GeneratePassthroughGeometryShader(1, 0)); + m_color_geometry_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Geometry, FramebufferShaderGen::GeneratePassthroughGeometryShader(0, 1)); + if (!m_texcoord_geometry_shader || !m_color_geometry_shader) + return false; + } + + m_texture_copy_pixel_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Pixel, FramebufferShaderGen::GenerateTextureCopyPixelShader()); + m_color_pixel_shader = g_renderer->CreateShaderFromSource( + ShaderStage::Pixel, FramebufferShaderGen::GenerateColorPixelShader()); + if (!m_texture_copy_pixel_shader || !m_color_pixel_shader) + return false; + + AbstractPipelineConfig config; + config.vertex_format = nullptr; + config.vertex_shader = m_texture_copy_vertex_shader.get(); + config.geometry_shader = nullptr; + config.pixel_shader = m_texture_copy_pixel_shader.get(); + config.rasterization_state = RenderState::GetNoCullRasterizationState(PrimitiveType::Triangles); + config.depth_state = RenderState::GetNoDepthTestingDepthState(); + config.blending_state = RenderState::GetNoBlendingBlendState(); + config.framebuffer_state = RenderState::GetRGBA8FramebufferState(); + config.usage = AbstractPipelineUsage::Utility; + m_copy_rgba8_pipeline = g_renderer->CreatePipeline(config); + if (!m_copy_rgba8_pipeline) + return false; + + if (UseGeometryShaderForEFBCopies()) + { + config.geometry_shader = m_texcoord_geometry_shader.get(); + m_rgba8_stereo_copy_pipeline = g_renderer->CreatePipeline(config); + if (!m_rgba8_stereo_copy_pipeline) + return false; + } + + if (m_host_config.backend_palette_conversion) + { + config.vertex_shader = m_screen_quad_vertex_shader.get(); + config.geometry_shader = nullptr; + + for (size_t i = 0; i < NUM_PALETTE_CONVERSION_SHADERS; i++) + { + auto shader = g_renderer->CreateShaderFromSource( + ShaderStage::Pixel, TextureConversionShaderTiled::GeneratePaletteConversionShader( + static_cast<TLUTFormat>(i), m_api_type)); + if (!shader) + return false; + + config.pixel_shader = shader.get(); + m_palette_conversion_pipelines[i] = g_renderer->CreatePipeline(config); + if (!m_palette_conversion_pipelines[i]) + return false; + } + } + + return true; +} - ss << "#define API_D3D " << (m_api_type == APIType::D3D ? 1 : 0) << "\n"; - ss << "#define API_OPENGL " << (m_api_type == APIType::OpenGL ? 1 : 0) << "\n"; - ss << "#define API_VULKAN " << (m_api_type == APIType::Vulkan ? 1 : 0) << "\n"; +const AbstractPipeline* ShaderCache::GetPaletteConversionPipeline(TLUTFormat format) +{ + ASSERT(static_cast<size_t>(format) < NUM_PALETTE_CONVERSION_SHADERS); + return m_palette_conversion_pipelines[static_cast<size_t>(format)].get(); +} - if (m_efb_multisamples > 1) +const AbstractShader* ShaderCache::GetTextureDecodingShader(TextureFormat format, + TLUTFormat palette_format) +{ + const auto key = std::make_pair(static_cast<u32>(format), static_cast<u32>(palette_format)); + auto iter = m_texture_decoding_shaders.find(key); + if (iter != m_texture_decoding_shaders.end()) + return iter->second.get(); + + std::string shader_source = + TextureConversionShaderTiled::GenerateDecodingShader(format, palette_format, APIType::OpenGL); + if (shader_source.empty()) { - ss << "#define MSAA_ENABLED 1" << std::endl; - ss << "#define MSAA_SAMPLES " << m_efb_multisamples << std::endl; - if (m_host_config.ssaa) - ss << "#define SSAA_ENABLED 1" << std::endl; + m_texture_decoding_shaders.emplace(key, nullptr); + return nullptr; } - ss << "#define EFB_LAYERS " << (m_host_config.stereo ? 2 : 1) << std::endl; + std::unique_ptr<AbstractShader> shader = + g_renderer->CreateShaderFromSource(ShaderStage::Compute, shader_source); + if (!shader) + { + m_texture_decoding_shaders.emplace(key, nullptr); + return nullptr; + } - return ss.str(); + auto iiter = m_texture_decoding_shaders.emplace(key, std::move(shader)); + return iiter.first->second.get(); } + } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/ShaderCache.h b/Source/Core/VideoCommon/ShaderCache.h index a91559c965..66caad93ac 100644 --- a/Source/Core/VideoCommon/ShaderCache.h +++ b/Source/Core/VideoCommon/ShaderCache.h @@ -25,12 +25,16 @@ #include "VideoCommon/GeometryShaderGen.h" #include "VideoCommon/PixelShaderGen.h" #include "VideoCommon/RenderState.h" +#include "VideoCommon/TextureCacheBase.h" +#include "VideoCommon/TextureConversionShader.h" +#include "VideoCommon/TextureConverterShaderGen.h" #include "VideoCommon/UberShaderPixel.h" #include "VideoCommon/UberShaderVertex.h" #include "VideoCommon/VertexShaderGen.h" class NativeVertexFormat; enum class AbstractTextureFormat : u32; +enum class TLUTFormat; namespace VideoCommon { @@ -44,8 +48,11 @@ public: bool Initialize(); void Shutdown(); - // Changes the shader host config. Shaders will be reloaded if there are changes. - void SetHostConfig(const ShaderHostConfig& host_config, u32 efb_multisamples); + // Compiles/loads cached shaders. + void InitializeShaderCache(); + + // Changes the shader host config. Shaders should be reloaded afterwards. + void SetHostConfig(const ShaderHostConfig& host_config) { m_host_config = host_config; } // Reloads/recreates all shaders and pipelines. void Reload(); @@ -53,9 +60,6 @@ public: // Retrieves all pending shaders/pipelines from the async compiler. void RetrieveAsyncShaders(); - // Get utility shader header based on current config. - std::string GetUtilityShaderHeader() const; - // Accesses ShaderGen shader caches const AbstractPipeline* GetPipelineForUid(const GXPipelineUid& uid); const AbstractPipeline* GetUberPipelineForUid(const GXUberPipelineUid& uid); @@ -64,7 +68,48 @@ public: // The optional will be empty if this pipeline is now background compiling. std::optional<const AbstractPipeline*> GetPipelineForUidAsync(const GXPipelineUid& uid); + // Shared shaders + const AbstractShader* GetScreenQuadVertexShader() const + { + return m_screen_quad_vertex_shader.get(); + } + const AbstractShader* GetTextureCopyVertexShader() const + { + return m_texture_copy_vertex_shader.get(); + } + const AbstractShader* GetEFBCopyVertexShader() const { return m_efb_copy_vertex_shader.get(); } + const AbstractShader* GetTexcoordGeometryShader() const + { + return m_texcoord_geometry_shader.get(); + } + const AbstractShader* GetTextureCopyPixelShader() const + { + return m_texture_copy_pixel_shader.get(); + } + const AbstractShader* GetColorGeometryShader() const { return m_color_geometry_shader.get(); } + const AbstractShader* GetColorPixelShader() const { return m_color_pixel_shader.get(); } + + // EFB copy to RAM/VRAM pipelines + const AbstractPipeline* + GetEFBCopyToVRAMPipeline(const TextureConversionShaderGen::TCShaderUid& uid); + const AbstractPipeline* GetEFBCopyToRAMPipeline(const EFBCopyParams& uid); + + // RGBA8 framebuffer copy pipelines + const AbstractPipeline* GetRGBA8CopyPipeline() const { return m_copy_rgba8_pipeline.get(); } + const AbstractPipeline* GetRGBA8StereoCopyPipeline() const + { + return m_rgba8_stereo_copy_pipeline.get(); + } + + // Palette texture conversion pipelines + const AbstractPipeline* GetPaletteConversionPipeline(TLUTFormat format); + + // Texture decoding compute shaders + const AbstractShader* GetTextureDecodingShader(TextureFormat format, TLUTFormat palette_format); + private: + static constexpr size_t NUM_PALETTE_CONVERSION_SHADERS = 3; + void WaitForAsyncCompiler(); void LoadShaderCaches(); void ClearShaderCaches(); @@ -74,6 +119,7 @@ private: void InvalidateCachedPipelines(); void ClearPipelineCaches(); void QueueUberShaderPipelines(); + bool CompileSharedPipelines(); // GX shader compiler methods std::unique_ptr<AbstractShader> CompileVertexShader(const VertexShaderUid& uid) const; @@ -93,6 +139,9 @@ private: const AbstractShader* CreateGeometryShader(const GeometryShaderUid& uid); bool NeedsGeometryShader(const GeometryShaderUid& uid) const; + // Should we use geometry shaders for EFB copies? + bool UseGeometryShaderForEFBCopies() const; + // GX pipeline compiler methods AbstractPipelineConfig GetGXPipelineConfig(const NativeVertexFormat* vertex_format, const AbstractShader* vertex_shader, @@ -130,10 +179,17 @@ private: // Configuration bits. APIType m_api_type = APIType::Nothing; ShaderHostConfig m_host_config = {}; - AbstractTextureFormat m_efb_depth_format; - u32 m_efb_multisamples = 1; std::unique_ptr<AsyncShaderCompiler> m_async_shader_compiler; + // Shared shaders + std::unique_ptr<AbstractShader> m_screen_quad_vertex_shader; + std::unique_ptr<AbstractShader> m_texture_copy_vertex_shader; + std::unique_ptr<AbstractShader> m_efb_copy_vertex_shader; + std::unique_ptr<AbstractShader> m_texcoord_geometry_shader; + std::unique_ptr<AbstractShader> m_color_geometry_shader; + std::unique_ptr<AbstractShader> m_texture_copy_pixel_shader; + std::unique_ptr<AbstractShader> m_color_pixel_shader; + // GX Shader Caches template <typename Uid> struct ShaderModuleCache @@ -157,6 +213,22 @@ private: std::map<GXUberPipelineUid, std::pair<std::unique_ptr<AbstractPipeline>, bool>> m_gx_uber_pipeline_cache; File::IOFile m_gx_pipeline_uid_cache_file; + + // EFB copy to VRAM/RAM pipelines + std::map<TextureConversionShaderGen::TCShaderUid, std::unique_ptr<AbstractPipeline>> + m_efb_copy_to_vram_pipelines; + std::map<EFBCopyParams, std::unique_ptr<AbstractPipeline>> m_efb_copy_to_ram_pipelines; + + // Copy pipeline for RGBA8 textures + std::unique_ptr<AbstractPipeline> m_copy_rgba8_pipeline; + std::unique_ptr<AbstractPipeline> m_rgba8_stereo_copy_pipeline; + + // Palette conversion pipelines + std::array<std::unique_ptr<AbstractPipeline>, NUM_PALETTE_CONVERSION_SHADERS> + m_palette_conversion_pipelines; + + // Texture decoding shaders + std::map<std::pair<u32, u32>, std::unique_ptr<AbstractShader>> m_texture_decoding_shaders; }; } // namespace VideoCommon diff --git a/Source/Core/VideoCommon/ShaderGenCommon.cpp b/Source/Core/VideoCommon/ShaderGenCommon.cpp index 54d87691de..ee4c3d32f8 100644 --- a/Source/Core/VideoCommon/ShaderGenCommon.cpp +++ b/Source/Core/VideoCommon/ShaderGenCommon.cpp @@ -34,6 +34,7 @@ ShaderHostConfig ShaderHostConfig::GetCurrent() g_ActiveConfig.backend_info.bSupportsDynamicSamplerIndexing; bits.backend_shader_framebuffer_fetch = g_ActiveConfig.backend_info.bSupportsFramebufferFetch; bits.backend_logic_op = g_ActiveConfig.backend_info.bSupportsLogicOp; + bits.backend_palette_conversion = g_ActiveConfig.backend_info.bSupportsPaletteConversion; return bits; } diff --git a/Source/Core/VideoCommon/ShaderGenCommon.h b/Source/Core/VideoCommon/ShaderGenCommon.h index 83416d06d4..216f791df5 100644 --- a/Source/Core/VideoCommon/ShaderGenCommon.h +++ b/Source/Core/VideoCommon/ShaderGenCommon.h @@ -181,7 +181,8 @@ union ShaderHostConfig u32 backend_dynamic_sampler_indexing : 1; u32 backend_shader_framebuffer_fetch : 1; u32 backend_logic_op : 1; - u32 pad : 10; + u32 backend_palette_conversion : 1; + u32 pad : 9; }; static ShaderHostConfig GetCurrent(); @@ -216,7 +217,7 @@ template <class T> inline void GenerateVSOutputMembers(T& object, APIType api_type, u32 texgens, const ShaderHostConfig& host_config, const char* qualifier) { - DefineOutputMember(object, api_type, qualifier, "float4", "pos", -1, "POSITION"); + DefineOutputMember(object, api_type, qualifier, "float4", "pos", -1, "SV_Position"); DefineOutputMember(object, api_type, qualifier, "float4", "colors_", 0, "COLOR", 0); DefineOutputMember(object, api_type, qualifier, "float4", "colors_", 1, "COLOR", 1); diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index a42438e0b1..3beaeedbf8 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -28,16 +28,21 @@ #include "Core/FifoPlayer/FifoRecorder.h" #include "Core/HW/Memmap.h" +#include "VideoCommon/AbstractFramebuffer.h" #include "VideoCommon/AbstractStagingTexture.h" #include "VideoCommon/BPMemory.h" -#include "VideoCommon/Debugger.h" -#include "VideoCommon/FramebufferManagerBase.h" +#include "VideoCommon/FramebufferManager.h" #include "VideoCommon/HiresTextures.h" +#include "VideoCommon/PixelShaderManager.h" #include "VideoCommon/RenderBase.h" #include "VideoCommon/SamplerCommon.h" +#include "VideoCommon/ShaderCache.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/TextureCacheBase.h" +#include "VideoCommon/TextureConversionShader.h" +#include "VideoCommon/TextureConverterShaderGen.h" #include "VideoCommon/TextureDecoder.h" +#include "VideoCommon/VertexManagerBase.h" #include "VideoCommon/VideoCommon.h" #include "VideoCommon/VideoConfig.h" @@ -50,8 +55,9 @@ std::unique_ptr<TextureCacheBase> g_texture_cache; std::bitset<8> TextureCacheBase::valid_bind_points; -TextureCacheBase::TCacheEntry::TCacheEntry(std::unique_ptr<AbstractTexture> tex) - : texture(std::move(tex)) +TextureCacheBase::TCacheEntry::TCacheEntry(std::unique_ptr<AbstractTexture> tex, + std::unique_ptr<AbstractFramebuffer> fb) + : texture(std::move(tex)), framebuffer(std::move(fb)) { } @@ -88,6 +94,25 @@ TextureCacheBase::TextureCacheBase() InvalidateAllBindPoints(); } +TextureCacheBase::~TextureCacheBase() +{ + HiresTexture::Shutdown(); + Invalidate(); + Common::FreeAlignedMemory(temp); + temp = nullptr; +} + +bool TextureCacheBase::Initialize() +{ + if (!CreateUtilityTextures()) + { + PanicAlert("Failed to create utility textures."); + return false; + } + + return true; +} + void TextureCacheBase::Invalidate() { FlushEFBCopies(); @@ -107,14 +132,6 @@ void TextureCacheBase::Invalidate() texture_pool.clear(); } -TextureCacheBase::~TextureCacheBase() -{ - HiresTexture::Shutdown(); - Invalidate(); - Common::FreeAlignedMemory(temp); - temp = nullptr; -} - void TextureCacheBase::OnConfigChanged(VideoConfig& config) { if (config.bHiresTextures != backup_config.hires_textures || @@ -138,14 +155,6 @@ void TextureCacheBase::OnConfigChanged(VideoConfig& config) g_ActiveConfig.bTexFmtOverlayCenter); } - if ((config.stereo_mode != StereoMode::Off) != backup_config.stereo_3d || - config.bStereoEFBMonoDepth != backup_config.efb_mono_depth) - { - g_texture_cache->DeleteShaders(); - if (!g_texture_cache->CompileShaders()) - PanicAlert("Failed to recompile one or more texture conversion shaders."); - } - SetBackupConfig(config); } @@ -242,7 +251,7 @@ TextureCacheBase::ApplyPaletteToEntry(TCacheEntry* entry, u8* palette, TLUTForma { TextureConfig new_config = entry->texture->GetConfig(); new_config.levels = 1; - new_config.rendertarget = true; + new_config.flags |= AbstractTextureFlag_RenderTarget; TCacheEntry* decoded_entry = AllocateCacheEntry(new_config); if (!decoded_entry) @@ -278,29 +287,27 @@ void TextureCacheBase::ScaleTextureCacheEntryTo(TextureCacheBase::TCacheEntry* e return; } - TextureConfig newconfig; - newconfig.width = new_width; - newconfig.height = new_height; - newconfig.layers = entry->GetNumLayers(); - newconfig.rendertarget = true; - - std::unique_ptr<AbstractTexture> new_texture = AllocateTexture(newconfig); - if (new_texture) - { - new_texture->ScaleRectangleFromTexture(entry->texture.get(), - entry->texture->GetConfig().GetRect(), - new_texture->GetConfig().GetRect()); - entry->texture.swap(new_texture); - - auto config = new_texture->GetConfig(); - // At this point new_texture has the old texture in it, - // we can potentially reuse this, so let's move it back to the pool - texture_pool.emplace(config, TexPoolEntry(std::move(new_texture))); - } - else + const TextureConfig newconfig(new_width, new_height, 1, entry->GetNumLayers(), 1, + AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget); + std::optional<TexPoolEntry> new_texture = AllocateTexture(newconfig); + if (!new_texture) { - ERROR_LOG(VIDEO, "Scaling failed"); + ERROR_LOG(VIDEO, "Scaling failed due to texture allocation failure"); + return; } + + // No need to convert the coordinates here since they'll be the same. + g_renderer->ScaleTexture(new_texture->framebuffer.get(), + new_texture->texture->GetConfig().GetRect(), entry->texture.get(), + entry->texture->GetConfig().GetRect()); + entry->texture.swap(new_texture->texture); + entry->framebuffer.swap(new_texture->framebuffer); + + // At this point new_texture has the old texture in it, + // we can potentially reuse this, so let's move it back to the pool + auto config = new_texture->texture->GetConfig(); + texture_pool.emplace( + config, TexPoolEntry(std::move(new_texture->texture), std::move(new_texture->framebuffer))); } TextureCacheBase::TCacheEntry* @@ -383,6 +390,17 @@ TextureCacheBase::DoPartialTextureUpdates(TCacheEntry* entry_to_update, u8* pale dst_y = 0; } + // If the source rectangle is outside of what we actually have in VRAM, skip the copy. + // The backend doesn't do any clamping, so if we don't, we'd pass out-of-range coordinates + // to the graphics driver, which can cause GPU resets. + if (static_cast<u32>(src_x) >= entry->native_width || + static_cast<u32>(src_y) >= entry->native_height || + static_cast<u32>(dst_x) >= entry_to_update->native_width || + static_cast<u32>(dst_y) >= entry_to_update->native_height) + { + continue; + } + u32 copy_width = std::min(entry->native_width - src_x, entry_to_update->native_width - dst_x); u32 copy_height = @@ -482,12 +500,79 @@ static u32 CalculateLevelSize(u32 level_0_size, u32 level) return std::max(level_0_size >> level, 1u); } +static void SetSamplerState(u32 index, float custom_tex_scale, bool custom_tex, + bool has_arbitrary_mips) +{ + const FourTexUnits& tex = bpmem.tex[index / 4]; + const TexMode0& tm0 = tex.texMode0[index % 4]; + + SamplerState state = {}; + state.Generate(bpmem, index); + + // Force texture filtering config option. + if (g_ActiveConfig.bForceFiltering) + { + state.min_filter = SamplerState::Filter::Linear; + state.mag_filter = SamplerState::Filter::Linear; + state.mipmap_filter = SamplerCommon::AreBpTexMode0MipmapsEnabled(tm0) ? + SamplerState::Filter::Linear : + SamplerState::Filter::Point; + } + + // Custom textures may have a greater number of mips + if (custom_tex) + state.max_lod = 255; + + // Anisotropic filtering option. + if (g_ActiveConfig.iMaxAnisotropy != 0 && !SamplerCommon::IsBpTexMode0PointFiltering(tm0)) + { + // https://www.opengl.org/registry/specs/EXT/texture_filter_anisotropic.txt + // For predictable results on all hardware/drivers, only use one of: + // GL_LINEAR + GL_LINEAR (No Mipmaps [Bilinear]) + // GL_LINEAR + GL_LINEAR_MIPMAP_LINEAR (w/ Mipmaps [Trilinear]) + // Letting the game set other combinations will have varying arbitrary results; + // possibly being interpreted as equal to bilinear/trilinear, implicitly + // disabling anisotropy, or changing the anisotropic algorithm employed. + state.min_filter = SamplerState::Filter::Linear; + state.mag_filter = SamplerState::Filter::Linear; + if (SamplerCommon::AreBpTexMode0MipmapsEnabled(tm0)) + state.mipmap_filter = SamplerState::Filter::Linear; + state.anisotropic_filtering = 1; + } + else + { + state.anisotropic_filtering = 0; + } + + if (has_arbitrary_mips && SamplerCommon::AreBpTexMode0MipmapsEnabled(tm0)) + { + // Apply a secondary bias calculated from the IR scale to pull inwards mipmaps + // that have arbitrary contents, eg. are used for fog effects where the + // distance they kick in at is important to preserve at any resolution. + // Correct this with the upscaling factor of custom textures. + s64 lod_offset = std::log2(g_renderer->GetEFBScale() / custom_tex_scale) * 256.f; + state.lod_bias = MathUtil::Clamp<s64>(state.lod_bias + lod_offset, -32768, 32767); + + // Anisotropic also pushes mips farther away so it cannot be used either + state.anisotropic_filtering = 0; + } + + g_renderer->SetSamplerState(index, state); +} + void TextureCacheBase::BindTextures() { for (u32 i = 0; i < bound_textures.size(); i++) { - if (IsValidBindPoint(i) && bound_textures[i]) - g_renderer->SetTexture(i, bound_textures[i]->texture.get()); + const TCacheEntry* tentry = bound_textures[i]; + if (IsValidBindPoint(i) && tentry) + { + g_renderer->SetTexture(i, tentry->texture.get()); + PixelShaderManager::SetTexDims(i, tentry->native_width, tentry->native_height); + + const float custom_tex_scale = tentry->GetWidth() / float(tentry->native_width); + SetSamplerState(i, custom_tex_scale, tentry->is_custom_tex, tentry->has_arbitrary_mips); + } } } @@ -668,8 +753,6 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::Load(const u32 stage) entry->frameCount = FRAMECOUNT_INVALID; bound_textures[stage] = entry; - GFX_DEBUGGER_PAUSE_AT(NEXT_TEXTURE_CHANGE, true); - // We need to keep track of invalided textures until they have actually been replaced or // re-loaded valid_bind_points.set(stage); @@ -957,25 +1040,17 @@ TextureCacheBase::GetTexture(u32 address, u32 width, u32 height, const TextureFo // banks, and if we're doing an copy we may as well just do the whole thing on the CPU, since // there's no conversion between formats. In the future this could be extended with a separate // shader, however. - bool decode_on_gpu = !hires_tex && g_ActiveConfig.UseGPUTextureDecoding() && - g_texture_cache->SupportsGPUTextureDecode(texformat, tlutfmt) && - !(from_tmem && texformat == TextureFormat::RGBA8); + const bool decode_on_gpu = !hires_tex && g_ActiveConfig.UseGPUTextureDecoding() && + !(from_tmem && texformat == TextureFormat::RGBA8); // create the entry/texture - TextureConfig config; - config.width = width; - config.height = height; - config.levels = texLevels; - config.format = hires_tex ? hires_tex->GetFormat() : AbstractTextureFormat::RGBA8; - - ArbitraryMipmapDetector arbitrary_mip_detector; - + const TextureConfig config(width, height, texLevels, 1, 1, + hires_tex ? hires_tex->GetFormat() : AbstractTextureFormat::RGBA8, 0); TCacheEntry* entry = AllocateCacheEntry(config); - GFX_DEBUGGER_PAUSE_AT(NEXT_NEW_TEXTURE, true); - if (!entry) return nullptr; + ArbitraryMipmapDetector arbitrary_mip_detector; const u8* tlut = &texMem[tlutaddr]; if (hires_tex) { @@ -989,14 +1064,10 @@ TextureCacheBase::GetTexture(u32 address, u32 width, u32 height, const TextureFo if (!hires_tex) { - if (decode_on_gpu) - { - u32 row_stride = bytes_per_block * (expandedWidth / bsw); - g_texture_cache->DecodeTextureOnGPU(entry, 0, src_data, texture_size, texformat, width, - height, expandedWidth, expandedHeight, row_stride, tlut, - tlutfmt); - } - else + if (!decode_on_gpu || + !DecodeTextureOnGPU(entry, 0, src_data, texture_size, texformat, width, height, + expandedWidth, expandedHeight, bytes_per_block * (expandedWidth / bsw), + tlut, tlutfmt)) { size_t decoded_texture_size = expandedWidth * sizeof(u32) * expandedHeight; @@ -1089,20 +1160,16 @@ TextureCacheBase::GetTexture(u32 address, u32 width, u32 height, const TextureFo const u32 expanded_mip_height = Common::AlignUp(mip_height, bsh); const u8*& mip_src_data = from_tmem ? ((level % 2) ? ptr_odd : ptr_even) : src_data; - size_t mip_size = + const u32 mip_size = TexDecoder_GetTextureSizeInBytes(expanded_mip_width, expanded_mip_height, texformat); - if (decode_on_gpu) - { - u32 row_stride = bytes_per_block * (expanded_mip_width / bsw); - g_texture_cache->DecodeTextureOnGPU(entry, level, mip_src_data, mip_size, texformat, - mip_width, mip_height, expanded_mip_width, - expanded_mip_height, row_stride, tlut, tlutfmt); - } - else + if (!decode_on_gpu || + !DecodeTextureOnGPU(entry, level, mip_src_data, mip_size, texformat, mip_width, + mip_height, expanded_mip_width, expanded_mip_height, + bytes_per_block * (expanded_mip_width / bsw), tlut, tlutfmt)) { // No need to call CheckTempSize here, as the whole buffer is preallocated at the beginning - size_t decoded_mip_size = expanded_mip_width * sizeof(u32) * expanded_mip_height; + const u32 decoded_mip_size = expanded_mip_width * sizeof(u32) * expanded_mip_height; TexDecoder_Decode(dst_buffer, mip_src_data, expanded_mip_width, expanded_mip_height, texformat, tlut, tlutfmt); entry->texture->Load(level, mip_width, mip_height, expanded_mip_width, dst_buffer, @@ -1133,6 +1200,8 @@ TextureCacheBase::GetTexture(u32 address, u32 width, u32 height, const TextureFo entry = DoPartialTextureUpdates(iter->second, &texMem[tlutaddr], tlutfmt); + // This should only be needed if the texture was updated, or used GPU decoding. + entry->texture->FinishedRendering(); return entry; } @@ -1300,7 +1369,7 @@ TextureCacheBase::GetTextureFromOverlappingTextures(const TextureLookupInformati // or as a container for overlapping textures, never need to be combined // with other textures TCacheEntry* stitched_entry = - CreateNormalTexture(tex_info, FramebufferManagerBase::GetEFBLayers()); + CreateNormalTexture(tex_info, g_framebuffer_manager->GetEFBLayers()); stitched_entry->may_have_overlapping_textures = false; // It is possible that some of the overlapping textures overlap each other. @@ -1385,6 +1454,17 @@ TextureCacheBase::GetTextureFromOverlappingTextures(const TextureLookupInformati dst_y = 0; } + // If the source rectangle is outside of what we actually have in VRAM, skip the copy. + // The backend doesn't do any clamping, so if we don't, we'd pass out-of-range coordinates + // to the graphics driver, which can cause GPU resets. + if (static_cast<u32>(src_x) >= entry->native_width || + static_cast<u32>(src_y) >= entry->native_height || + static_cast<u32>(dst_x) >= stitched_entry->native_width || + static_cast<u32>(dst_y) >= stitched_entry->native_height) + { + continue; + } + u32 copy_width = std::min(entry->native_width - src_x, stitched_entry->native_width - dst_x); u32 copy_height = std::min(entry->native_height - src_y, stitched_entry->native_height - dst_y); @@ -1450,6 +1530,7 @@ TextureCacheBase::GetTextureFromOverlappingTextures(const TextureLookupInformati return nullptr; } + stitched_entry->texture->FinishedRendering(); return stitched_entry; } @@ -1457,17 +1538,10 @@ TextureCacheBase::TCacheEntry* TextureCacheBase::CreateNormalTexture(const TextureLookupInformation& tex_info, u32 layers) { // create the entry/texture - TextureConfig config; - config.width = tex_info.native_width; - config.height = tex_info.native_height; - config.levels = tex_info.computed_levels; - config.format = AbstractTextureFormat::RGBA8; - config.rendertarget = true; - config.layers = layers; - + const TextureConfig config(tex_info.native_width, tex_info.native_height, + tex_info.computed_levels, layers, 1, AbstractTextureFormat::RGBA8, + AbstractTextureFlag_RenderTarget); TCacheEntry* entry = AllocateCacheEntry(config); - GFX_DEBUGGER_PAUSE_AT(NEXT_NEW_TEXTURE, true); - if (!entry) return nullptr; @@ -1500,15 +1574,15 @@ TextureCacheBase::GetTextureFromMemory(const TextureLookupInformation& tex_info) // banks, and if we're doing an copy we may as well just do the whole thing on the CPU, since // there's no conversion between formats. In the future this could be extended with a separate // shader, however. - bool decode_on_gpu = g_ActiveConfig.UseGPUTextureDecoding() && - g_texture_cache->SupportsGPUTextureDecode(tex_info.full_format.texfmt, - tex_info.full_format.tlutfmt) && - !(tex_info.from_tmem && tex_info.full_format.texfmt == TextureFormat::RGBA8); + const bool decode_on_gpu = + g_ActiveConfig.UseGPUTextureDecoding() && + !(tex_info.from_tmem && tex_info.full_format.texfmt == TextureFormat::RGBA8); // Since it's coming from RAM, it can only have one layer (no stereo). TCacheEntry* entry = CreateNormalTexture(tex_info, 1); entry->may_have_overlapping_textures = false; LoadTextureLevelZeroFromMemory(entry, tex_info, decode_on_gpu); + entry->texture->FinishedRendering(); return entry; } @@ -1518,15 +1592,13 @@ void TextureCacheBase::LoadTextureLevelZeroFromMemory(TCacheEntry* entry_to_upda { const u8* tlut = &texMem[tex_info.tlut_address]; - if (decode_on_gpu) - { - u32 row_stride = tex_info.bytes_per_block * (tex_info.expanded_width / tex_info.block_width); - g_texture_cache->DecodeTextureOnGPU( - entry_to_update, 0, tex_info.src_data, tex_info.total_bytes, tex_info.full_format.texfmt, - tex_info.native_width, tex_info.native_height, tex_info.expanded_width, - tex_info.expanded_height, row_stride, tlut, tex_info.full_format.tlutfmt); - } - else + if (!decode_on_gpu || + DecodeTextureOnGPU(entry_to_update, 0, tex_info.src_data, tex_info.total_bytes, + tex_info.full_format.texfmt, tex_info.native_width, tex_info.native_height, + tex_info.expanded_width, tex_info.expanded_height, + tex_info.bytes_per_block * + (tex_info.expanded_width / tex_info.block_width), + tlut, tex_info.full_format.tlutfmt)) { size_t decoded_texture_size = tex_info.expanded_width * sizeof(u32) * tex_info.expanded_height; CheckTempSize(decoded_texture_size); @@ -1547,12 +1619,12 @@ void TextureCacheBase::LoadTextureLevelZeroFromMemory(TCacheEntry* entry_to_upda } } -TextureCacheBase::CopyFilterCoefficientArray TextureCacheBase::GetRAMCopyFilterCoefficients( - const CopyFilterCoefficients::Values& coefficients) const +EFBCopyFilterCoefficients +TextureCacheBase::GetRAMCopyFilterCoefficients(const CopyFilterCoefficients::Values& coefficients) { // To simplify the backend, we precalculate the three coefficients in common. Coefficients 0, 1 // are for the row above, 2, 3, 4 are for the current pixel, and 5, 6 are for the row below. - return {{ + return EFBCopyFilterCoefficients{ static_cast<float>(static_cast<u32>(coefficients[0]) + static_cast<u32>(coefficients[1])) / 64.0f, static_cast<float>(static_cast<u32>(coefficients[2]) + static_cast<u32>(coefficients[3]) + @@ -1560,31 +1632,31 @@ TextureCacheBase::CopyFilterCoefficientArray TextureCacheBase::GetRAMCopyFilterC 64.0f, static_cast<float>(static_cast<u32>(coefficients[5]) + static_cast<u32>(coefficients[6])) / 64.0f, - }}; + }; } -TextureCacheBase::CopyFilterCoefficientArray TextureCacheBase::GetVRAMCopyFilterCoefficients( - const CopyFilterCoefficients::Values& coefficients) const +EFBCopyFilterCoefficients +TextureCacheBase::GetVRAMCopyFilterCoefficients(const CopyFilterCoefficients::Values& coefficients) { // If the user disables the copy filter, only apply it to the VRAM copy. // This way games which are sensitive to changes to the RAM copy of the XFB will be unaffected. - CopyFilterCoefficientArray res = GetRAMCopyFilterCoefficients(coefficients); + EFBCopyFilterCoefficients res = GetRAMCopyFilterCoefficients(coefficients); if (!g_ActiveConfig.bDisableCopyFilter) return res; // Disabling the copy filter in options should not ignore the values the game sets completely, // as some games use the filter coefficients to control the brightness of the screen. Instead, // add all coefficients to the middle sample, so the deflicker/vertical filter has no effect. - res[1] += res[0] + res[2]; - res[0] = 0; - res[2] = 0; + res.middle = res.upper + res.middle + res.lower; + res.upper = 0.0f; + res.lower = 0.0f; return res; } -bool TextureCacheBase::NeedsCopyFilterInShader(const CopyFilterCoefficientArray& coefficients) const +bool TextureCacheBase::NeedsCopyFilterInShader(const EFBCopyFilterCoefficients& coefficients) { // If the top/bottom coefficients are zero, no point sampling/blending from these rows. - return coefficients[0] != 0 || coefficients[2] != 0; + return coefficients.upper != 0 || coefficients.lower != 0; } void TextureCacheBase::CopyRenderTargetToTexture( @@ -1726,12 +1798,8 @@ void TextureCacheBase::CopyRenderTargetToTexture( if (copy_to_vram) { // create the texture - TextureConfig config; - config.rendertarget = true; - config.width = scaled_tex_w; - config.height = scaled_tex_h; - config.layers = FramebufferManagerBase::GetEFBLayers(); - + const TextureConfig config(scaled_tex_w, scaled_tex_h, 1, g_framebuffer_manager->GetEFBLayers(), + 1, AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget); entry = AllocateCacheEntry(config); if (entry) { @@ -1776,7 +1844,7 @@ void TextureCacheBase::CopyRenderTargetToTexture( if (copy_to_ram) { - CopyFilterCoefficientArray coefficients = GetRAMCopyFilterCoefficients(filter_coefficients); + EFBCopyFilterCoefficients coefficients = GetRAMCopyFilterCoefficients(filter_coefficients); PEControl::PixelFormat srcFormat = bpmem.zcontrol.pixel_format; EFBCopyParams format(srcFormat, dstFormat, is_depth_copy, isIntensity, NeedsCopyFilterInShader(coefficients)); @@ -1916,11 +1984,6 @@ void TextureCacheBase::FlushEFBCopies() m_pending_efb_copies.clear(); } -TextureConfig TextureCacheBase::GetEncodingTextureConfig() -{ - return TextureConfig(EFB_WIDTH * 4, 1024, 1, 1, 1, AbstractTextureFormat::BGRA8, true); -} - void TextureCacheBase::WriteEFBCopyToRAM(u8* dst_ptr, u32 width, u32 height, u32 stride, std::unique_ptr<AbstractStagingTexture> staging_texture) { @@ -1979,8 +2042,8 @@ std::unique_ptr<AbstractStagingTexture> TextureCacheBase::GetEFBCopyStagingTextu return ptr; } - std::unique_ptr<AbstractStagingTexture> tex = - g_renderer->CreateStagingTexture(StagingTextureType::Readback, GetEncodingTextureConfig()); + std::unique_ptr<AbstractStagingTexture> tex = g_renderer->CreateStagingTexture( + StagingTextureType::Readback, m_efb_encoding_texture->GetConfig()); if (!tex) WARN_LOG(VIDEO, "Failed to create EFB copy staging texture"); @@ -2037,37 +2100,50 @@ void TextureCacheBase::UninitializeXFBMemory(u8* dst, u32 stride, u32 bytes_per_ TextureCacheBase::TCacheEntry* TextureCacheBase::AllocateCacheEntry(const TextureConfig& config) { - std::unique_ptr<AbstractTexture> texture = AllocateTexture(config); - - if (!texture) - { + std::optional<TexPoolEntry> alloc = AllocateTexture(config); + if (!alloc) return nullptr; - } - TCacheEntry* cacheEntry = new TCacheEntry(std::move(texture)); + + TCacheEntry* cacheEntry = + new TCacheEntry(std::move(alloc->texture), std::move(alloc->framebuffer)); cacheEntry->textures_by_hash_iter = textures_by_hash.end(); cacheEntry->id = last_entry_id++; return cacheEntry; } -std::unique_ptr<AbstractTexture> TextureCacheBase::AllocateTexture(const TextureConfig& config) +std::optional<TextureCacheBase::TexPoolEntry> +TextureCacheBase::AllocateTexture(const TextureConfig& config) { TexPool::iterator iter = FindMatchingTextureFromPool(config); - std::unique_ptr<AbstractTexture> entry; if (iter != texture_pool.end()) { - entry = std::move(iter->second.texture); + auto entry = std::move(iter->second); texture_pool.erase(iter); + return std::move(entry); } - else + + std::unique_ptr<AbstractTexture> texture = g_renderer->CreateTexture(config); + if (!texture) { - entry = g_renderer->CreateTexture(config); - if (!entry) - return nullptr; + WARN_LOG(VIDEO, "Failed to allocate a %ux%ux%u texture", config.width, config.height, + config.layers); + return {}; + } - INCSTAT(stats.numTexturesCreated); + std::unique_ptr<AbstractFramebuffer> framebuffer; + if (config.IsRenderTarget()) + { + framebuffer = g_renderer->CreateFramebuffer(texture.get(), nullptr); + if (!framebuffer) + { + WARN_LOG(VIDEO, "Failed to allocate a %ux%ux%u framebuffer", config.width, config.height, + config.layers); + return {}; + } } - return entry; + INCSTAT(stats.numTexturesCreated); + return TexPoolEntry(std::move(texture), std::move(framebuffer)); } TextureCacheBase::TexPool::iterator @@ -2080,7 +2156,7 @@ TextureCacheBase::FindMatchingTextureFromPool(const TextureConfig& config) // As non-render-target textures are usually static, this should not matter much. auto range = texture_pool.equal_range(config); auto matching_iter = std::find_if(range.first, range.second, [](const auto& iter) { - return iter.first.rendertarget || iter.second.frameCount != FRAMECOUNT_INVALID; + return iter.first.IsRenderTarget() || iter.second.frameCount != FRAMECOUNT_INVALID; }); return matching_iter != range.second ? matching_iter : texture_pool.end(); } @@ -2171,7 +2247,8 @@ TextureCacheBase::InvalidateTexture(TexAddrCache::iterator iter, bool discard_pe } auto config = entry->texture->GetConfig(); - texture_pool.emplace(config, TexPoolEntry(std::move(entry->texture))); + texture_pool.emplace(config, + TexPoolEntry(std::move(entry->texture), std::move(entry->framebuffer))); // Don't delete if there's a pending EFB copy, as we need the TCacheEntry alive. if (!entry->pending_efb_copy) @@ -2180,6 +2257,283 @@ TextureCacheBase::InvalidateTexture(TexAddrCache::iterator iter, bool discard_pe return textures_by_address.erase(iter); } +bool TextureCacheBase::CreateUtilityTextures() +{ + constexpr TextureConfig encoding_texture_config( + EFB_WIDTH * 4, 1024, 1, 1, 1, AbstractTextureFormat::BGRA8, AbstractTextureFlag_RenderTarget); + m_efb_encoding_texture = g_renderer->CreateTexture(encoding_texture_config); + if (!m_efb_encoding_texture) + return false; + + m_efb_encoding_framebuffer = g_renderer->CreateFramebuffer(m_efb_encoding_texture.get(), nullptr); + if (!m_efb_encoding_framebuffer) + return false; + + if (g_ActiveConfig.backend_info.bSupportsGPUTextureDecoding) + { + constexpr TextureConfig decoding_texture_config( + 1024, 1024, 1, 1, 1, AbstractTextureFormat::RGBA8, AbstractTextureFlag_ComputeImage); + m_decoding_texture = g_renderer->CreateTexture(decoding_texture_config); + if (!m_decoding_texture) + return false; + } + + return true; +} + +void TextureCacheBase::CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_copy, + const EFBRectangle& src_rect, bool scale_by_half, + EFBCopyFormat dst_format, bool is_intensity, float gamma, + bool clamp_top, bool clamp_bottom, + const EFBCopyFilterCoefficients& filter_coefficients) +{ + // Flush EFB pokes first, as they're expected to be included. + g_framebuffer_manager->FlushEFBPokes(); + + // Get the pipeline which we will be using. If the compilation failed, this will be null. + const AbstractPipeline* copy_pipeline = + g_shader_cache->GetEFBCopyToVRAMPipeline(TextureConversionShaderGen::GetShaderUid( + dst_format, is_depth_copy, is_intensity, scale_by_half, + NeedsCopyFilterInShader(filter_coefficients))); + if (!copy_pipeline) + { + WARN_LOG(VIDEO, "Skipping EFB copy to VRAM due to missing pipeline."); + return; + } + + const auto scaled_src_rect = g_renderer->ConvertEFBRectangle(src_rect); + AbstractTexture* src_texture = + is_depth_copy ? g_framebuffer_manager->ResolveEFBDepthTexture(scaled_src_rect) : + g_framebuffer_manager->ResolveEFBColorTexture(scaled_src_rect); + + g_renderer->BeginUtilityDrawing(); + + // Fill uniform buffer. + struct Uniforms + { + float src_left, src_top, src_width, src_height; + float filter_coefficients[3]; + float gamma_rcp; + float clamp_top; + float clamp_bottom; + float pixel_height; + u32 padding; + }; + Uniforms uniforms; + const auto framebuffer_rect = g_renderer->ConvertFramebufferRectangle( + scaled_src_rect, g_framebuffer_manager->GetEFBFramebuffer()); + const float rcp_efb_width = 1.0f / static_cast<float>(g_framebuffer_manager->GetEFBWidth()); + const float rcp_efb_height = 1.0f / static_cast<float>(g_framebuffer_manager->GetEFBHeight()); + uniforms.src_left = framebuffer_rect.left * rcp_efb_width; + uniforms.src_top = framebuffer_rect.top * rcp_efb_height; + uniforms.src_width = framebuffer_rect.GetWidth() * rcp_efb_width; + uniforms.src_height = framebuffer_rect.GetHeight() * rcp_efb_height; + uniforms.filter_coefficients[0] = filter_coefficients.upper; + uniforms.filter_coefficients[1] = filter_coefficients.middle; + uniforms.filter_coefficients[2] = filter_coefficients.lower; + uniforms.gamma_rcp = 1.0f / gamma; + uniforms.clamp_top = clamp_top ? framebuffer_rect.top * rcp_efb_height : 0.0f; + uniforms.clamp_bottom = clamp_bottom ? framebuffer_rect.bottom * rcp_efb_height : 1.0f; + uniforms.pixel_height = g_ActiveConfig.bCopyEFBScaled ? rcp_efb_height : 1.0f / EFB_HEIGHT; + uniforms.padding = 0; + g_vertex_manager->UploadUtilityUniforms(&uniforms, sizeof(uniforms)); + + // Use the copy pipeline to render the VRAM copy. + g_renderer->SetAndDiscardFramebuffer(entry->framebuffer.get()); + g_renderer->SetViewportAndScissor(entry->framebuffer->GetRect()); + g_renderer->SetPipeline(copy_pipeline); + g_renderer->SetTexture(0, src_texture); + g_renderer->SetSamplerState(0, scale_by_half ? RenderState::GetLinearSamplerState() : + RenderState::GetPointSamplerState()); + g_renderer->Draw(0, 3); + g_renderer->EndUtilityDrawing(); + entry->texture->FinishedRendering(); +} + +void TextureCacheBase::CopyEFB(AbstractStagingTexture* dst, const EFBCopyParams& params, + u32 native_width, u32 bytes_per_row, u32 num_blocks_y, + u32 memory_stride, const EFBRectangle& src_rect, bool scale_by_half, + float y_scale, float gamma, bool clamp_top, bool clamp_bottom, + const EFBCopyFilterCoefficients& filter_coefficients) +{ + // Flush EFB pokes first, as they're expected to be included. + g_framebuffer_manager->FlushEFBPokes(); + + // Get the pipeline which we will be using. If the compilation failed, this will be null. + const AbstractPipeline* copy_pipeline = g_shader_cache->GetEFBCopyToRAMPipeline(params); + if (!copy_pipeline) + { + WARN_LOG(VIDEO, "Skipping EFB copy to VRAM due to missing pipeline."); + return; + } + + const auto scaled_src_rect = g_renderer->ConvertEFBRectangle(src_rect); + AbstractTexture* src_texture = + params.depth ? g_framebuffer_manager->ResolveEFBDepthTexture(scaled_src_rect) : + g_framebuffer_manager->ResolveEFBColorTexture(scaled_src_rect); + + g_renderer->BeginUtilityDrawing(); + + // Fill uniform buffer. + struct Uniforms + { + std::array<s32, 4> position_uniform; + float y_scale; + float gamma_rcp; + float clamp_top; + float clamp_bottom; + float filter_coefficients[3]; + u32 padding; + }; + Uniforms encoder_params; + const auto framebuffer_rect = g_renderer->ConvertFramebufferRectangle( + scaled_src_rect, g_framebuffer_manager->GetEFBFramebuffer()); + const float rcp_efb_height = 1.0f / static_cast<float>(g_framebuffer_manager->GetEFBHeight()); + encoder_params.position_uniform[0] = scaled_src_rect.left; + encoder_params.position_uniform[1] = scaled_src_rect.top; + encoder_params.position_uniform[2] = static_cast<s32>(native_width); + encoder_params.position_uniform[3] = scale_by_half ? 2 : 1; + encoder_params.y_scale = y_scale; + encoder_params.gamma_rcp = 1.0f / gamma; + encoder_params.clamp_top = clamp_top ? framebuffer_rect.top * rcp_efb_height : 0.0f; + encoder_params.clamp_bottom = clamp_bottom ? framebuffer_rect.bottom * rcp_efb_height : 1.0f; + encoder_params.filter_coefficients[0] = filter_coefficients.upper; + encoder_params.filter_coefficients[1] = filter_coefficients.middle; + encoder_params.filter_coefficients[2] = filter_coefficients.lower; + g_vertex_manager->UploadUtilityUniforms(&encoder_params, sizeof(encoder_params)); + + // We also linear filtering for both box filtering and downsampling higher resolutions to 1x + // TODO: This only produces perfect downsampling for 2x IR, other resolutions will need more + // complex down filtering to average all pixels and produce the correct result. + const bool linear_filter = + (scale_by_half && !params.depth) || g_renderer->GetEFBScale() != 1 || y_scale > 1.0f; + + // Because the shader uses gl_FragCoord and we read it back, we must render to the lower-left. + const u32 render_width = bytes_per_row / sizeof(u32); + const u32 render_height = num_blocks_y; + const auto encode_rect = MathUtil::Rectangle<int>(0, 0, render_width, render_height); + + // Render to GPU texture, and then copy to CPU-accessible texture. + g_renderer->SetAndDiscardFramebuffer(m_efb_encoding_framebuffer.get()); + g_renderer->SetViewportAndScissor(encode_rect); + g_renderer->SetPipeline(copy_pipeline); + g_renderer->SetTexture(0, src_texture); + g_renderer->SetSamplerState(0, linear_filter ? RenderState::GetLinearSamplerState() : + RenderState::GetPointSamplerState()); + g_renderer->Draw(0, 3); + dst->CopyFromTexture(m_efb_encoding_texture.get(), encode_rect, 0, 0, encode_rect); + g_renderer->EndUtilityDrawing(); + + // Flush if there's sufficient draws between this copy and the last. + g_vertex_manager->OnEFBCopyToRAM(); +} + +bool TextureCacheBase::ConvertTexture(TCacheEntry* entry, TCacheEntry* unconverted, + const void* palette, TLUTFormat format) +{ + DEBUG_ASSERT(entry->texture->GetConfig().IsRenderTarget() && entry->framebuffer); + if (!g_ActiveConfig.backend_info.bSupportsPaletteConversion) + { + ERROR_LOG(VIDEO, "Backend does not support palette conversion!"); + return false; + } + + g_renderer->BeginUtilityDrawing(); + + const u32 palette_size = unconverted->format == TextureFormat::I4 ? 32 : 512; + u32 texel_buffer_offset; + if (!g_vertex_manager->UploadTexelBuffer(palette, palette_size, + TexelBufferFormat::TEXEL_BUFFER_FORMAT_R16_UINT, + &texel_buffer_offset)) + { + ERROR_LOG(VIDEO, "Texel buffer upload failed"); + return false; + } + + struct Uniforms + { + float multiplier; + u32 texel_buffer_offset; + u32 pad[2]; + }; + static_assert(std::is_standard_layout<Uniforms>::value); + Uniforms uniforms = {}; + uniforms.multiplier = unconverted->format == TextureFormat::I4 ? 15.0f : 255.0f; + uniforms.texel_buffer_offset = texel_buffer_offset; + g_vertex_manager->UploadUtilityUniforms(&uniforms, sizeof(uniforms)); + + g_renderer->SetAndDiscardFramebuffer(entry->framebuffer.get()); + g_renderer->SetViewportAndScissor(entry->texture->GetRect()); + g_renderer->SetPipeline(g_shader_cache->GetPaletteConversionPipeline(format)); + g_renderer->SetTexture(1, unconverted->texture.get()); + g_renderer->SetSamplerState(1, RenderState::GetPointSamplerState()); + g_renderer->Draw(0, 3); + g_renderer->EndUtilityDrawing(); + entry->texture->FinishedRendering(); + return true; +} + +bool TextureCacheBase::DecodeTextureOnGPU(TCacheEntry* entry, u32 dst_level, const u8* data, + u32 data_size, TextureFormat format, u32 width, + u32 height, u32 aligned_width, u32 aligned_height, + u32 row_stride, const u8* palette, + TLUTFormat palette_format) +{ + const auto* info = TextureConversionShaderTiled::GetDecodingShaderInfo(format); + if (!info) + return false; + + const AbstractShader* shader = g_shader_cache->GetTextureDecodingShader(format, palette_format); + if (!shader) + return false; + + // Copy to GPU-visible buffer, aligned to the data type. + const u32 bytes_per_buffer_elem = + VertexManagerBase::GetTexelBufferElementSize(info->buffer_format); + + // Allocate space in stream buffer, and copy texture + palette across. + u32 src_offset = 0, palette_offset = 0; + if (info->palette_size > 0) + { + if (!g_vertex_manager->UploadTexelBuffer(data, data_size, info->buffer_format, &src_offset, + palette, info->palette_size, + TEXEL_BUFFER_FORMAT_R16_UINT, &palette_offset)) + { + return false; + } + } + else + { + if (!g_vertex_manager->UploadTexelBuffer(data, data_size, info->buffer_format, &src_offset)) + return false; + } + + // Set up uniforms. + struct Uniforms + { + u32 dst_width, dst_height; + u32 src_width, src_height; + u32 src_offset, src_row_stride; + u32 palette_offset, unused; + } uniforms = {width, height, aligned_width, + aligned_height, src_offset, row_stride / bytes_per_buffer_elem, + palette_offset}; + g_vertex_manager->UploadUtilityUniforms(&uniforms, sizeof(uniforms)); + g_renderer->SetComputeImageTexture(m_decoding_texture.get(), false, true); + + auto dispatch_groups = + TextureConversionShaderTiled::GetDispatchCount(info, aligned_width, aligned_height); + g_renderer->DispatchComputeShader(shader, dispatch_groups.first, dispatch_groups.second, 1); + + // Copy from decoding texture -> final texture + // This is because we don't want to have to create compute view for every layer + const auto copy_rect = entry->texture->GetConfig().GetMipRect(dst_level); + entry->texture->CopyRectangleFromTexture(m_decoding_texture.get(), copy_rect, 0, 0, copy_rect, 0, + dst_level); + entry->texture->FinishedRendering(); + return true; +} + u32 TextureCacheBase::TCacheEntry::BytesPerRow() const { const u32 blockW = TexDecoder_GetBlockWidthInTexels(format.texfmt); @@ -2272,3 +2626,9 @@ u64 TextureCacheBase::TCacheEntry::CalculateHash() const return temp_hash; } } + +TextureCacheBase::TexPoolEntry::TexPoolEntry(std::unique_ptr<AbstractTexture> tex, + std::unique_ptr<AbstractFramebuffer> fb) + : texture(std::move(tex)), framebuffer(std::move(fb)) +{ +} diff --git a/Source/Core/VideoCommon/TextureCacheBase.h b/Source/Core/VideoCommon/TextureCacheBase.h index fc9c49311b..bd3ceab046 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.h +++ b/Source/Core/VideoCommon/TextureCacheBase.h @@ -23,6 +23,7 @@ #include "VideoCommon/VideoCommon.h" struct VideoConfig; +class AbstractFramebuffer; class AbstractStagingTexture; struct TextureAndTLUTFormat @@ -68,6 +69,14 @@ struct EFBCopyParams bool copy_filter; }; +// Reduced version of the full coefficient array, with a single value for each row. +struct EFBCopyFilterCoefficients +{ + float upper; + float middle; + float lower; +}; + struct TextureLookupInformation { u32 address; @@ -110,13 +119,11 @@ private: static const int FRAMECOUNT_INVALID = 0; public: - // Reduced version of the full coefficient array, reduced to a single value for each row. - using CopyFilterCoefficientArray = std::array<float, 3>; - struct TCacheEntry { // common members std::unique_ptr<AbstractTexture> texture; + std::unique_ptr<AbstractFramebuffer> framebuffer; u32 addr; u32 size_in_bytes; u64 base_hash; @@ -157,7 +164,8 @@ public: u32 pending_efb_copy_height = 0; bool pending_efb_copy_invalidated = false; - explicit TCacheEntry(std::unique_ptr<AbstractTexture> tex); + explicit TCacheEntry(std::unique_ptr<AbstractTexture> tex, + std::unique_ptr<AbstractFramebuffer> fb); ~TCacheEntry(); @@ -214,7 +222,10 @@ public: AbstractTextureFormat GetFormat() const { return texture->GetConfig().format; } }; - virtual ~TextureCacheBase(); // needs virtual for DX11 dtor + TextureCacheBase(); + virtual ~TextureCacheBase(); + + bool Initialize(); void OnConfigChanged(VideoConfig& config); @@ -224,15 +235,6 @@ public: void Invalidate(); - virtual void CopyEFB(AbstractStagingTexture* dst, const EFBCopyParams& params, u32 native_width, - u32 bytes_per_row, u32 num_blocks_y, u32 memory_stride, - const EFBRectangle& src_rect, bool scale_by_half, float y_scale, float gamma, - bool clamp_top, bool clamp_bottom, - const CopyFilterCoefficientArray& filter_coefficients) = 0; - - virtual bool CompileShaders() = 0; - virtual void DeleteShaders() = 0; - TCacheEntry* Load(const u32 stage); static void InvalidateAllBindPoints() { valid_bind_points.reset(); } static bool IsValidBindPoint(u32 i) { return valid_bind_points.test(i); } @@ -262,39 +264,39 @@ public: bool clamp_top, bool clamp_bottom, const CopyFilterCoefficients::Values& filter_coefficients); - virtual void ConvertTexture(TCacheEntry* entry, TCacheEntry* unconverted, const void* palette, - TLUTFormat format) = 0; - - // Returns true if the texture data and palette formats are supported by the GPU decoder. - virtual bool SupportsGPUTextureDecode(TextureFormat format, TLUTFormat palette_format) - { - return false; - } - - // Decodes the specified data to the GPU texture specified by entry. - // width, height are the size of the image in pixels. - // aligned_width, aligned_height are the size of the image in pixels, aligned to the block size. - // row_stride is the number of bytes for a row of blocks, not pixels. - virtual void DecodeTextureOnGPU(TCacheEntry* entry, u32 dst_level, const u8* data, - size_t data_size, TextureFormat format, u32 width, u32 height, - u32 aligned_width, u32 aligned_height, u32 row_stride, - const u8* palette, TLUTFormat palette_format) - { - } - void ScaleTextureCacheEntryTo(TCacheEntry* entry, u32 new_width, u32 new_height); // Flushes all pending EFB copies to emulated RAM. void FlushEFBCopies(); - // Returns a texture config suitable for drawing a RAM EFB copy into. - static TextureConfig GetEncodingTextureConfig(); + // Returns false if the top/bottom row coefficients are zero. + static bool NeedsCopyFilterInShader(const EFBCopyFilterCoefficients& coefficients); protected: - TextureCacheBase(); + // Applies a palette to an EFB copy/texture. + bool ConvertTexture(TCacheEntry* entry, TCacheEntry* unconverted, const void* palette, + TLUTFormat format); - // Returns false if the top/bottom row coefficients are zero. - bool NeedsCopyFilterInShader(const CopyFilterCoefficientArray& coefficients) const; + // Decodes the specified data to the GPU texture specified by entry. + // Returns false if the configuration is not supported. + // width, height are the size of the image in pixels. + // aligned_width, aligned_height are the size of the image in pixels, aligned to the block size. + // row_stride is the number of bytes for a row of blocks, not pixels. + bool DecodeTextureOnGPU(TCacheEntry* entry, u32 dst_level, const u8* data, u32 data_size, + TextureFormat format, u32 width, u32 height, u32 aligned_width, + u32 aligned_height, u32 row_stride, const u8* palette, + TLUTFormat palette_format); + + virtual void CopyEFB(AbstractStagingTexture* dst, const EFBCopyParams& params, u32 native_width, + u32 bytes_per_row, u32 num_blocks_y, u32 memory_stride, + const EFBRectangle& src_rect, bool scale_by_half, float y_scale, float gamma, + bool clamp_top, bool clamp_bottom, + const EFBCopyFilterCoefficients& filter_coefficients); + virtual void CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_copy, + const EFBRectangle& src_rect, bool scale_by_half, + EFBCopyFormat dst_format, bool is_intensity, float gamma, + bool clamp_top, bool clamp_bottom, + const EFBCopyFilterCoefficients& filter_coefficients); alignas(16) u8* temp = nullptr; size_t temp_size = 0; @@ -307,13 +309,17 @@ private: struct TexPoolEntry { std::unique_ptr<AbstractTexture> texture; + std::unique_ptr<AbstractFramebuffer> framebuffer; int frameCount = FRAMECOUNT_INVALID; - TexPoolEntry(std::unique_ptr<AbstractTexture> tex) : texture(std::move(tex)) {} + + TexPoolEntry(std::unique_ptr<AbstractTexture> tex, std::unique_ptr<AbstractFramebuffer> fb); }; using TexAddrCache = std::multimap<u32, TCacheEntry*>; using TexHashCache = std::multimap<u64, TCacheEntry*>; using TexPool = std::unordered_multimap<TextureConfig, TexPoolEntry>; + bool CreateUtilityTextures(); + void SetBackupConfig(const VideoConfig& config); TCacheEntry* ApplyPaletteToEntry(TCacheEntry* entry, u8* palette, TLUTFormat tlutfmt); @@ -325,7 +331,7 @@ private: void CheckTempSize(size_t required_size); TCacheEntry* AllocateCacheEntry(const TextureConfig& config); - std::unique_ptr<AbstractTexture> AllocateTexture(const TextureConfig& config); + std::optional<TexPoolEntry> AllocateTexture(const TextureConfig& config); TexPool::iterator FindMatchingTextureFromPool(const TextureConfig& config); TexAddrCache::iterator GetTexCacheIter(TCacheEntry* entry); @@ -334,12 +340,6 @@ private: std::pair<TexAddrCache::iterator, TexAddrCache::iterator> FindOverlappingTextures(u32 addr, u32 size_in_bytes); - virtual void CopyEFBToCacheEntry(TCacheEntry* entry, bool is_depth_copy, - const EFBRectangle& src_rect, bool scale_by_half, - EFBCopyFormat dst_format, bool is_intensity, float gamma, - bool clamp_top, bool clamp_bottom, - const CopyFilterCoefficientArray& filter_coefficients) = 0; - // Removes and unlinks texture from texture cache and returns it to the pool TexAddrCache::iterator InvalidateTexture(TexAddrCache::iterator t_iter, bool discard_pending_efb_copy = false); @@ -347,10 +347,10 @@ private: void UninitializeXFBMemory(u8* dst, u32 stride, u32 bytes_per_row, u32 num_blocks_y); // Precomputing the coefficients for the previous, current, and next lines for the copy filter. - CopyFilterCoefficientArray - GetRAMCopyFilterCoefficients(const CopyFilterCoefficients::Values& coefficients) const; - CopyFilterCoefficientArray - GetVRAMCopyFilterCoefficients(const CopyFilterCoefficients::Values& coefficients) const; + static EFBCopyFilterCoefficients + GetRAMCopyFilterCoefficients(const CopyFilterCoefficients::Values& coefficients); + static EFBCopyFilterCoefficients + GetVRAMCopyFilterCoefficients(const CopyFilterCoefficients::Values& coefficients); // Flushes a pending EFB copy to RAM from the host to the guest RAM. void WriteEFBCopyToRAM(u8* dst_ptr, u32 width, u32 height, u32 stride, @@ -385,6 +385,13 @@ private: }; BackupConfig backup_config = {}; + // Encoding texture used for EFB copies to RAM. + std::unique_ptr<AbstractTexture> m_efb_encoding_texture; + std::unique_ptr<AbstractFramebuffer> m_efb_encoding_framebuffer; + + // Decoding texture used for GPU texture decoding. + std::unique_ptr<AbstractTexture> m_decoding_texture; + // Pool of readback textures used for deferred EFB copies. std::vector<std::unique_ptr<AbstractStagingTexture>> m_efb_copy_staging_texture_pool; diff --git a/Source/Core/VideoCommon/TextureConfig.cpp b/Source/Core/VideoCommon/TextureConfig.cpp index 0407576af6..98a9dbc4f3 100644 --- a/Source/Core/VideoCommon/TextureConfig.cpp +++ b/Source/Core/VideoCommon/TextureConfig.cpp @@ -9,8 +9,8 @@ bool TextureConfig::operator==(const TextureConfig& o) const { - return std::tie(width, height, levels, layers, samples, format, rendertarget) == - std::tie(o.width, o.height, o.levels, o.layers, o.samples, o.format, o.rendertarget); + return std::tie(width, height, levels, layers, samples, format, flags) == + std::tie(o.width, o.height, o.levels, o.layers, o.samples, o.format, o.flags); } bool TextureConfig::operator!=(const TextureConfig& o) const @@ -38,8 +38,3 @@ size_t TextureConfig::GetMipStride(u32 level) const { return AbstractTexture::CalculateStrideForFormat(format, std::max(width >> level, 1u)); } - -bool TextureConfig::IsMultisampled() const -{ - return samples > 1; -} diff --git a/Source/Core/VideoCommon/TextureConfig.h b/Source/Core/VideoCommon/TextureConfig.h index a2358ff3b0..ee52537285 100644 --- a/Source/Core/VideoCommon/TextureConfig.h +++ b/Source/Core/VideoCommon/TextureConfig.h @@ -34,13 +34,19 @@ enum class StagingTextureType Mutable // Optimize for CPU reads, GPU writes, allow slow CPU reads }; +enum AbstractTextureFlag : u32 +{ + AbstractTextureFlag_RenderTarget = (1 << 0), // Texture is used as a framebuffer. + AbstractTextureFlag_ComputeImage = (1 << 1), // Texture is used as a compute image. +}; + struct TextureConfig { constexpr TextureConfig() = default; constexpr TextureConfig(u32 width_, u32 height_, u32 levels_, u32 layers_, u32 samples_, - AbstractTextureFormat format_, bool rendertarget_) + AbstractTextureFormat format_, u32 flags_) : width(width_), height(height_), levels(levels_), layers(layers_), samples(samples_), - format(format_), rendertarget(rendertarget_) + format(format_), flags(flags_) { } @@ -50,7 +56,10 @@ struct TextureConfig MathUtil::Rectangle<int> GetMipRect(u32 level) const; size_t GetStride() const; size_t GetMipStride(u32 level) const; - bool IsMultisampled() const; + + bool IsMultisampled() const { return samples > 1; } + bool IsRenderTarget() const { return (flags & AbstractTextureFlag_RenderTarget) != 0; } + bool IsComputeImage() const { return (flags & AbstractTextureFlag_ComputeImage) != 0; } u32 width = 0; u32 height = 0; @@ -58,7 +67,7 @@ struct TextureConfig u32 layers = 1; u32 samples = 1; AbstractTextureFormat format = AbstractTextureFormat::RGBA8; - bool rendertarget = false; + u32 flags = 0; }; namespace std @@ -71,7 +80,7 @@ struct hash<TextureConfig> result_type operator()(const argument_type& c) const noexcept { - const u64 id = static_cast<u64>(c.rendertarget) << 63 | static_cast<u64>(c.format) << 50 | + const u64 id = static_cast<u64>(c.flags) << 58 | static_cast<u64>(c.format) << 50 | static_cast<u64>(c.layers) << 48 | static_cast<u64>(c.levels) << 32 | static_cast<u64>(c.height) << 16 | static_cast<u64>(c.width); return std::hash<u64>{}(id); diff --git a/Source/Core/VideoCommon/TextureConversionShader.cpp b/Source/Core/VideoCommon/TextureConversionShader.cpp index e57cbdf603..7359c2aea3 100644 --- a/Source/Core/VideoCommon/TextureConversionShader.cpp +++ b/Source/Core/VideoCommon/TextureConversionShader.cpp @@ -15,7 +15,9 @@ #include "VideoCommon/RenderBase.h" #include "VideoCommon/TextureCacheBase.h" #include "VideoCommon/TextureConversionShader.h" +#include "VideoCommon/VertexManagerBase.h" #include "VideoCommon/VideoCommon.h" +#include "VideoCommon/VideoConfig.h" #define WRITE p += sprintf @@ -59,21 +61,10 @@ u16 GetEncodedSampleCount(EFBCopyFormat format) static void WriteHeader(char*& p, APIType ApiType) { - if (ApiType == APIType::OpenGL) + if (ApiType == APIType::OpenGL || ApiType == APIType::Vulkan) { // left, top, of source rectangle within source texture // width of the destination rectangle, scale_factor (1 or 2) - WRITE(p, "uniform int4 position;\n"); - WRITE(p, "uniform float y_scale;\n"); - WRITE(p, "uniform float gamma_rcp;\n"); - WRITE(p, "uniform float2 clamp_tb;\n"); - WRITE(p, "uniform float3 filter_coefficients;\n"); - WRITE(p, "#define samp0 samp9\n"); - WRITE(p, "SAMPLER_BINDING(9) uniform sampler2DArray samp0;\n"); - WRITE(p, "FRAGMENT_OUTPUT_LOCATION(0) out vec4 ocol0;\n"); - } - else if (ApiType == APIType::Vulkan) - { WRITE(p, "UBO_BINDING(std140, 1) uniform PSBlock {\n"); WRITE(p, " int4 position;\n"); WRITE(p, " float y_scale;\n"); @@ -81,8 +72,9 @@ static void WriteHeader(char*& p, APIType ApiType) WRITE(p, " float2 clamp_tb;\n"); WRITE(p, " float3 filter_coefficients;\n"); WRITE(p, "};\n"); + WRITE(p, "VARYING_LOCATION(0) in float3 v_tex0;\n"); WRITE(p, "SAMPLER_BINDING(0) uniform sampler2DArray samp0;\n"); - WRITE(p, "FRAGMENT_OUTPUT_LOCATION(0) out vec4 ocol0;\n"); + WRITE(p, "FRAGMENT_OUTPUT_LOCATION(0) out float4 ocol0;\n"); } else // D3D { @@ -147,7 +139,7 @@ static void WriteSampleFunction(char*& p, const EFBCopyParams& params, APIType A else { // Handle D3D depth inversion. - if (ApiType == APIType::D3D || ApiType == APIType::Vulkan) + if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange) WRITE(p, "1.0 - ("); else WRITE(p, "("); @@ -225,7 +217,9 @@ static void WriteSwizzler(char*& p, const EFBCopyParams& params, EFBCopyFormat f else // D3D { WRITE(p, "void main(\n"); - WRITE(p, " out float4 ocol0 : SV_Target, in float4 rawpos : SV_Position)\n"); + WRITE(p, " in float3 v_tex0 : TEXCOORD0,\n"); + WRITE(p, " in float4 rawpos : SV_Position,\n"); + WRITE(p, " out float4 ocol0 : SV_Target)\n"); WRITE(p, "{\n" " int2 sampleUv;\n" " int2 uv1 = int2(rawpos.xy);\n"); @@ -846,38 +840,65 @@ const char* GenerateEncodingShader(const EFBCopyParams& params, APIType api_type // NOTE: In these uniforms, a row refers to a row of blocks, not texels. static const char decoding_shader_header[] = R"( -#ifdef VULKAN - -layout(std140, push_constant) uniform PushConstants { - uvec2 dst_size; - uvec2 src_size; - uint src_offset; - uint src_row_stride; - uint palette_offset; -} push_constants; -#define u_dst_size (push_constants.dst_size) -#define u_src_size (push_constants.src_size) -#define u_src_offset (push_constants.src_offset) -#define u_src_row_stride (push_constants.src_row_stride) -#define u_palette_offset (push_constants.palette_offset) +#if defined(PALETTE_FORMAT_IA8) || defined(PALETTE_FORMAT_RGB565) || defined(PALETTE_FORMAT_RGB5A3) +#define HAS_PALETTE 1 +#endif -TEXEL_BUFFER_BINDING(0) uniform usamplerBuffer s_input_buffer; -TEXEL_BUFFER_BINDING(1) uniform usamplerBuffer s_palette_buffer; +#ifdef API_D3D +cbuffer UBO : register(b0) { +#else +UBO_BINDING(std140, 1) uniform UBO { +#endif + uint2 u_dst_size; + uint2 u_src_size; + uint u_src_offset; + uint u_src_row_stride; + uint u_palette_offset; +}; + +#ifdef API_D3D + +Buffer<uint4> s_input_buffer : register(t0); +#ifdef HAS_PALETTE +Buffer<uint4> s_palette_buffer : register(t1); +#endif -IMAGE_BINDING(rgba8, 0) uniform writeonly image2DArray output_image; +RWTexture2DArray<unorm float4> output_image : register(u0); + +// Helpers for reading/writing. +#define texelFetch(buffer, pos) buffer.Load(pos) +#define imageStore(image, coords, value) image[coords] = value +#define GROUP_MEMORY_BARRIER_WITH_SYNC GroupMemoryBarrierWithGroupSync(); +#define GROUP_SHARED groupshared + +#define DEFINE_MAIN(lx, ly) \ + [numthreads(lx, ly, 1)] \ + void main(uint3 gl_WorkGroupID : SV_GroupId, \ + uint3 gl_LocalInvocationID : SV_GroupThreadID, \ + uint3 gl_GlobalInvocationID : SV_DispatchThreadID) + +uint bitfieldExtract(uint val, int off, int size) +{ + // This built-in function is only support in OpenGL 4.0+ and ES 3.1+\n" + // Microsoft's HLSL compiler automatically optimises this to a bitfield extract instruction. + uint mask = uint((1 << size) - 1); + return uint(val >> off) & mask; +} #else -uniform uvec2 u_dst_size; -uniform uvec2 u_src_size; -uniform uint u_src_offset; -uniform uint u_src_row_stride; -uniform uint u_palette_offset; +TEXEL_BUFFER_BINDING(0) uniform usamplerBuffer s_input_buffer; +#ifdef HAS_PALETTE +TEXEL_BUFFER_BINDING(1) uniform usamplerBuffer s_palette_buffer; +#endif +IMAGE_BINDING(rgba8, 0) uniform writeonly image2DArray output_image; -SAMPLER_BINDING(9) uniform usamplerBuffer s_input_buffer; -SAMPLER_BINDING(10) uniform usamplerBuffer s_palette_buffer; +#define GROUP_MEMORY_BARRIER_WITH_SYNC memoryBarrierShared(); barrier(); +#define GROUP_SHARED shared -layout(rgba8, binding = 0) uniform writeonly image2DArray output_image; +#define DEFINE_MAIN(lx, ly) \ + layout(local_size_x = lx, local_size_y = ly) in; \ + void main() #endif @@ -908,10 +929,10 @@ uint Convert6To8(uint v) return (v << 2) | (v >> 4); } -uint GetTiledTexelOffset(uvec2 block_size, uvec2 coords) +uint GetTiledTexelOffset(uint2 block_size, uint2 coords) { - uvec2 block = coords / block_size; - uvec2 offset = coords % block_size; + uint2 block = coords / block_size; + uint2 offset = coords % block_size; uint buffer_pos = u_src_offset; buffer_pos += block.y * u_src_row_stride; buffer_pos += block.x * (block_size.x * block_size.y); @@ -920,16 +941,16 @@ uint GetTiledTexelOffset(uvec2 block_size, uvec2 coords) return buffer_pos; } -uvec4 GetPaletteColor(uint index) +uint4 GetPaletteColor(uint index) { // Fetch and swap BE to LE. uint val = Swap16(texelFetch(s_palette_buffer, int(u_palette_offset + index)).x); - uvec4 color; + uint4 color; #if defined(PALETTE_FORMAT_IA8) uint a = bitfieldExtract(val, 8, 8); uint i = bitfieldExtract(val, 0, 8); - color = uvec4(i, i, i, a); + color = uint4(i, i, i, a); #elif defined(PALETTE_FORMAT_RGB565) color.x = Convert5To8(bitfieldExtract(val, 11, 5)); color.y = Convert6To8(bitfieldExtract(val, 5, 6)); @@ -953,29 +974,27 @@ uvec4 GetPaletteColor(uint index) } #else // Not used. - color = uvec4(0, 0, 0, 0); + color = uint4(0, 0, 0, 0); #endif return color; } -vec4 GetPaletteColorNormalized(uint index) +float4 GetPaletteColorNormalized(uint index) { - uvec4 color = GetPaletteColor(index); - return vec4(color) / 255.0; + uint4 color = GetPaletteColor(index); + return float4(color) / 255.0; } )"; static const std::map<TextureFormat, DecodingShaderInfo> s_decoding_shader_info{ {TextureFormat::I4, - {BUFFER_FORMAT_R8_UINT, 0, 8, 8, false, + {TEXEL_BUFFER_FORMAT_R8_UINT, 0, 8, 8, false, R"( - layout(local_size_x = 8, local_size_y = 8) in; - - void main() + DEFINE_MAIN(8, 8) { - uvec2 coords = gl_GlobalInvocationID.xy; + uint2 coords = gl_GlobalInvocationID.xy; // Tiled in 8x8 blocks, 4 bits per pixel // We need to do the tiling manually here because the texel size is smaller than @@ -996,108 +1015,98 @@ static const std::map<TextureFormat, DecodingShaderInfo> s_decoding_shader_info{ else i = Convert4To8((val & 0x0Fu)); - uvec4 color = uvec4(i, i, i, i); - vec4 norm_color = vec4(color) / 255.0; + uint4 color = uint4(i, i, i, i); + float4 norm_color = float4(color) / 255.0; - imageStore(output_image, ivec3(ivec2(coords), 0), norm_color); + imageStore(output_image, int3(int2(coords), 0), norm_color); } )"}}, {TextureFormat::IA4, - {BUFFER_FORMAT_R8_UINT, 0, 8, 8, false, + {TEXEL_BUFFER_FORMAT_R8_UINT, 0, 8, 8, false, R"( - layout(local_size_x = 8, local_size_y = 8) in; - - void main() + DEFINE_MAIN(8, 8) { - uvec2 coords = gl_GlobalInvocationID.xy; + uint2 coords = gl_GlobalInvocationID.xy; // Tiled in 8x4 blocks, 8 bits per pixel - uint buffer_pos = GetTiledTexelOffset(uvec2(8u, 4u), coords); + uint buffer_pos = GetTiledTexelOffset(uint2(8u, 4u), coords); uint val = texelFetch(s_input_buffer, int(buffer_pos)).x; uint i = Convert4To8((val & 0x0Fu)); uint a = Convert4To8((val >> 4)); - uvec4 color = uvec4(i, i, i, a); - vec4 norm_color = vec4(color) / 255.0; + uint4 color = uint4(i, i, i, a); + float4 norm_color = float4(color) / 255.0; - imageStore(output_image, ivec3(ivec2(coords), 0), norm_color); + imageStore(output_image, int3(int2(coords), 0), norm_color); } )"}}, {TextureFormat::I8, - {BUFFER_FORMAT_R8_UINT, 0, 8, 8, false, + {TEXEL_BUFFER_FORMAT_R8_UINT, 0, 8, 8, false, R"( - layout(local_size_x = 8, local_size_y = 8) in; - - void main() + DEFINE_MAIN(8, 8) { - uvec2 coords = gl_GlobalInvocationID.xy; + uint2 coords = gl_GlobalInvocationID.xy; // Tiled in 8x4 blocks, 8 bits per pixel - uint buffer_pos = GetTiledTexelOffset(uvec2(8u, 4u), coords); + uint buffer_pos = GetTiledTexelOffset(uint2(8u, 4u), coords); uint i = texelFetch(s_input_buffer, int(buffer_pos)).x; - uvec4 color = uvec4(i, i, i, i); - vec4 norm_color = vec4(color) / 255.0; + uint4 color = uint4(i, i, i, i); + float4 norm_color = float4(color) / 255.0; - imageStore(output_image, ivec3(ivec2(coords), 0), norm_color); + imageStore(output_image, int3(int2(coords), 0), norm_color); } )"}}, {TextureFormat::IA8, - {BUFFER_FORMAT_R16_UINT, 0, 8, 8, false, + {TEXEL_BUFFER_FORMAT_R16_UINT, 0, 8, 8, false, R"( - layout(local_size_x = 8, local_size_y = 8) in; - - void main() + DEFINE_MAIN(8, 8) { - uvec2 coords = gl_GlobalInvocationID.xy; + uint2 coords = gl_GlobalInvocationID.xy; // Tiled in 4x4 blocks, 16 bits per pixel - uint buffer_pos = GetTiledTexelOffset(uvec2(4u, 4u), coords); + uint buffer_pos = GetTiledTexelOffset(uint2(4u, 4u), coords); uint val = texelFetch(s_input_buffer, int(buffer_pos)).x; uint a = (val & 0xFFu); uint i = (val >> 8); - uvec4 color = uvec4(i, i, i, a); - vec4 norm_color = vec4(color) / 255.0; - imageStore(output_image, ivec3(ivec2(coords), 0), norm_color); + uint4 color = uint4(i, i, i, a); + float4 norm_color = float4(color) / 255.0; + imageStore(output_image, int3(int2(coords), 0), norm_color); } )"}}, {TextureFormat::RGB565, - {BUFFER_FORMAT_R16_UINT, 0, 8, 8, false, + {TEXEL_BUFFER_FORMAT_R16_UINT, 0, 8, 8, false, R"( - layout(local_size_x = 8, local_size_y = 8) in; - - void main() + DEFINE_MAIN(8, 8) { - uvec2 coords = gl_GlobalInvocationID.xy; + uint2 coords = gl_GlobalInvocationID.xy; // Tiled in 4x4 blocks - uint buffer_pos = GetTiledTexelOffset(uvec2(4u, 4u), coords); + uint buffer_pos = GetTiledTexelOffset(uint2(4u, 4u), coords); uint val = Swap16(texelFetch(s_input_buffer, int(buffer_pos)).x); - uvec4 color; + uint4 color; color.x = Convert5To8(bitfieldExtract(val, 11, 5)); color.y = Convert6To8(bitfieldExtract(val, 5, 6)); color.z = Convert5To8(bitfieldExtract(val, 0, 5)); color.a = 255u; - vec4 norm_color = vec4(color) / 255.0; - imageStore(output_image, ivec3(ivec2(coords), 0), norm_color); + float4 norm_color = float4(color) / 255.0; + imageStore(output_image, int3(int2(coords), 0), norm_color); } )"}}, {TextureFormat::RGB5A3, - {BUFFER_FORMAT_R16_UINT, 0, 8, 8, false, + {TEXEL_BUFFER_FORMAT_R16_UINT, 0, 8, 8, false, R"( - layout(local_size_x = 8, local_size_y = 8) in; - - void main() + DEFINE_MAIN(8, 8) { - uvec2 coords = gl_GlobalInvocationID.xy; + uint2 coords = gl_GlobalInvocationID.xy; // Tiled in 4x4 blocks - uint buffer_pos = GetTiledTexelOffset(uvec2(4u, 4u), coords); + uint buffer_pos = GetTiledTexelOffset(uint2(4u, 4u), coords); uint val = Swap16(texelFetch(s_input_buffer, int(buffer_pos)).x); - uvec4 color; + uint4 color; if ((val & 0x8000u) != 0u) { color.x = Convert5To8(bitfieldExtract(val, 10, 5)); @@ -1113,19 +1122,17 @@ static const std::map<TextureFormat, DecodingShaderInfo> s_decoding_shader_info{ color.b = Convert4To8(bitfieldExtract(val, 0, 4)); } - vec4 norm_color = vec4(color) / 255.0; - imageStore(output_image, ivec3(ivec2(coords), 0), norm_color); + float4 norm_color = float4(color) / 255.0; + imageStore(output_image, int3(int2(coords), 0), norm_color); } )"}}, {TextureFormat::RGBA8, - {BUFFER_FORMAT_R16_UINT, 0, 8, 8, false, + {TEXEL_BUFFER_FORMAT_R16_UINT, 0, 8, 8, false, R"( - layout(local_size_x = 8, local_size_y = 8) in; - - void main() + DEFINE_MAIN(8, 8) { - uvec2 coords = gl_GlobalInvocationID.xy; + uint2 coords = gl_GlobalInvocationID.xy; // Tiled in 4x4 blocks // We can't use the normal calculation function, as these are packed as the AR channels @@ -1144,18 +1151,18 @@ static const std::map<TextureFormat, DecodingShaderInfo> s_decoding_shader_info{ uint val1 = texelFetch(s_input_buffer, int(buffer_pos + 0u)).x; uint val2 = texelFetch(s_input_buffer, int(buffer_pos + 16u)).x; - uvec4 color; + uint4 color; color.a = (val1 & 0xFFu); color.r = (val1 >> 8); color.g = (val2 & 0xFFu); color.b = (val2 >> 8); - vec4 norm_color = vec4(color) / 255.0; - imageStore(output_image, ivec3(ivec2(coords), 0), norm_color); + float4 norm_color = float4(color) / 255.0; + imageStore(output_image, int3(int2(coords), 0), norm_color); } )"}}, {TextureFormat::CMPR, - {BUFFER_FORMAT_R32G32_UINT, 0, 64, 1, true, + {TEXEL_BUFFER_FORMAT_R32G32_UINT, 0, 64, 1, true, R"( // In the compute version of this decoder, we flatten the blocks to a one-dimension array. // Each group is subdivided into 16, and the first thread in each group fetches the DXT data. @@ -1167,17 +1174,15 @@ static const std::map<TextureFormat, DecodingShaderInfo> s_decoding_shader_info{ #define BLOCK_SIZE (BLOCK_SIZE_X * BLOCK_SIZE_Y) #define BLOCKS_PER_GROUP (GROUP_SIZE / BLOCK_SIZE) - layout(local_size_x = GROUP_SIZE, local_size_y = 1) in; - - shared uvec2 shared_temp[BLOCKS_PER_GROUP]; - uint DXTBlend(uint v1, uint v2) { // 3/8 blend, which is close to 1/3 return ((v1 * 3u + v2 * 5u) >> 3); } - void main() + GROUP_SHARED uint2 shared_temp[BLOCKS_PER_GROUP]; + + DEFINE_MAIN(GROUP_SIZE, 8) { uint local_thread_id = gl_LocalInvocationID.x; uint block_in_group = local_thread_id / BLOCK_SIZE; @@ -1188,7 +1193,7 @@ static const std::map<TextureFormat, DecodingShaderInfo> s_decoding_shader_info{ // from the block size of the overall texture (4 vs 8). We can however use a multiply and // subtraction to avoid the modulo for calculating the block's X coordinate. uint blocks_wide = u_src_size.x / BLOCK_SIZE_X; - uvec2 block_coords; + uint2 block_coords; block_coords.y = block_index / blocks_wide; block_coords.x = block_index - (block_coords.y * blocks_wide); @@ -1196,8 +1201,8 @@ static const std::map<TextureFormat, DecodingShaderInfo> s_decoding_shader_info{ if (thread_in_block == 0u) { // Calculate tiled block coordinates. - uvec2 tile_block_coords = block_coords / 2u; - uvec2 subtile_block_coords = block_coords % 2u; + uint2 tile_block_coords = block_coords / 2u; + uint2 subtile_block_coords = block_coords % 2u; uint buffer_pos = u_src_offset; buffer_pos += tile_block_coords.y * u_src_row_stride; buffer_pos += tile_block_coords.x * 4u; @@ -1205,16 +1210,15 @@ static const std::map<TextureFormat, DecodingShaderInfo> s_decoding_shader_info{ buffer_pos += subtile_block_coords.x; // Read the entire DXT block to shared memory. - uvec2 raw_data = texelFetch(s_input_buffer, int(buffer_pos)).xy; + uint2 raw_data = texelFetch(s_input_buffer, int(buffer_pos)).xy; shared_temp[block_in_group] = raw_data; } // Ensure store is completed before the remaining threads in the block continue. - memoryBarrierShared(); - barrier(); + GROUP_MEMORY_BARRIER_WITH_SYNC; // Unpack colors and swap BE to LE. - uvec2 raw_data = shared_temp[block_in_group]; + uint2 raw_data = shared_temp[block_in_group]; uint swapped = ((raw_data.x & 0xFF00FF00u) >> 8) | ((raw_data.x & 0x00FF00FFu) << 8); uint c1 = swapped & 0xFFFFu; uint c2 = swapped >> 16; @@ -1230,18 +1234,18 @@ static const std::map<TextureFormat, DecodingShaderInfo> s_decoding_shader_info{ // Determine the four colors the block can use. // It's quicker to just precalculate all four colors rather than branching on the index. // NOTE: These must be masked with 0xFF. This is done at the normalization stage below. - uvec4 color0, color1, color2, color3; - color0 = uvec4(red1, green1, blue1, 255u); - color1 = uvec4(red2, green2, blue2, 255u); + uint4 color0, color1, color2, color3; + color0 = uint4(red1, green1, blue1, 255u); + color1 = uint4(red2, green2, blue2, 255u); if (c1 > c2) { - color2 = uvec4(DXTBlend(red2, red1), DXTBlend(green2, green1), DXTBlend(blue2, blue1), 255u); - color3 = uvec4(DXTBlend(red1, red2), DXTBlend(green1, green2), DXTBlend(blue1, blue2), 255u); + color2 = uint4(DXTBlend(red2, red1), DXTBlend(green2, green1), DXTBlend(blue2, blue1), 255u); + color3 = uint4(DXTBlend(red1, red2), DXTBlend(green1, green2), DXTBlend(blue1, blue2), 255u); } else { - color2 = uvec4((red1 + red2) / 2u, (green1 + green2) / 2u, (blue1 + blue2) / 2u, 255u); - color3 = uvec4((red1 + red2) / 2u, (green1 + green2) / 2u, (blue1 + blue2) / 2u, 0u); + color2 = uint4((red1 + red2) / 2u, (green1 + green2) / 2u, (blue1 + blue2) / 2u, 255u); + color3 = uint4((red1 + red2) / 2u, (green1 + green2) / 2u, (blue1 + blue2) / 2u, 0u); } // Calculate the texel coordinates that we will write to. @@ -1257,7 +1261,7 @@ static const std::map<TextureFormat, DecodingShaderInfo> s_decoding_shader_info{ // Select the un-normalized color from the precalculated color array. // Using a switch statement here removes the need for dynamic indexing of an array. - uvec4 color; + uint4 color; switch (index) { case 0u: color = color0; break; @@ -1268,19 +1272,17 @@ static const std::map<TextureFormat, DecodingShaderInfo> s_decoding_shader_info{ } // Normalize and write to the output image. - vec4 norm_color = vec4(color & 0xFFu) / 255.0; - imageStore(output_image, ivec3(ivec2(uvec2(global_x, global_y)), 0), norm_color); + float4 norm_color = float4(color & 0xFFu) / 255.0; + imageStore(output_image, int3(int2(uint2(global_x, global_y)), 0), norm_color); } )"}}, {TextureFormat::C4, - {BUFFER_FORMAT_R8_UINT, static_cast<u32>(TexDecoder_GetPaletteSize(TextureFormat::C4)), 8, 8, - false, + {TEXEL_BUFFER_FORMAT_R8_UINT, static_cast<u32>(TexDecoder_GetPaletteSize(TextureFormat::C4)), + 8, 8, false, R"( - layout(local_size_x = 8, local_size_y = 8) in; - - void main() + DEFINE_MAIN(8, 8) { - uvec2 coords = gl_GlobalInvocationID.xy; + uint2 coords = gl_GlobalInvocationID.xy; // Tiled in 8x8 blocks, 4 bits per pixel // We need to do the tiling manually here because the texel size is smaller than @@ -1296,58 +1298,52 @@ static const std::map<TextureFormat, DecodingShaderInfo> s_decoding_shader_info{ // Select high nibble for odd texels, low for even. uint val = texelFetch(s_input_buffer, int(buffer_pos)).x; uint index = ((coords.x & 1u) == 0u) ? (val >> 4) : (val & 0x0Fu); - vec4 norm_color = GetPaletteColorNormalized(index); - imageStore(output_image, ivec3(ivec2(coords), 0), norm_color); + float4 norm_color = GetPaletteColorNormalized(index); + imageStore(output_image, int3(int2(coords), 0), norm_color); } )"}}, {TextureFormat::C8, - {BUFFER_FORMAT_R8_UINT, static_cast<u32>(TexDecoder_GetPaletteSize(TextureFormat::C8)), 8, 8, - false, + {TEXEL_BUFFER_FORMAT_R8_UINT, static_cast<u32>(TexDecoder_GetPaletteSize(TextureFormat::C8)), + 8, 8, false, R"( - layout(local_size_x = 8, local_size_y = 8) in; - - void main() + DEFINE_MAIN(8, 8) { - uvec2 coords = gl_GlobalInvocationID.xy; + uint2 coords = gl_GlobalInvocationID.xy; // Tiled in 8x4 blocks, 8 bits per pixel - uint buffer_pos = GetTiledTexelOffset(uvec2(8u, 4u), coords); + uint buffer_pos = GetTiledTexelOffset(uint2(8u, 4u), coords); uint index = texelFetch(s_input_buffer, int(buffer_pos)).x; - vec4 norm_color = GetPaletteColorNormalized(index); - imageStore(output_image, ivec3(ivec2(coords), 0), norm_color); + float4 norm_color = GetPaletteColorNormalized(index); + imageStore(output_image, int3(int2(coords), 0), norm_color); } )"}}, {TextureFormat::C14X2, - {BUFFER_FORMAT_R16_UINT, static_cast<u32>(TexDecoder_GetPaletteSize(TextureFormat::C14X2)), 8, - 8, false, + {TEXEL_BUFFER_FORMAT_R16_UINT, + static_cast<u32>(TexDecoder_GetPaletteSize(TextureFormat::C14X2)), 8, 8, false, R"( - layout(local_size_x = 8, local_size_y = 8) in; - - void main() + DEFINE_MAIN(8, 8) { - uvec2 coords = gl_GlobalInvocationID.xy; + uint2 coords = gl_GlobalInvocationID.xy; // Tiled in 4x4 blocks, 16 bits per pixel - uint buffer_pos = GetTiledTexelOffset(uvec2(4u, 4u), coords); + uint buffer_pos = GetTiledTexelOffset(uint2(4u, 4u), coords); uint index = Swap16(texelFetch(s_input_buffer, int(buffer_pos)).x) & 0x3FFFu; - vec4 norm_color = GetPaletteColorNormalized(index); - imageStore(output_image, ivec3(ivec2(coords), 0), norm_color); + float4 norm_color = GetPaletteColorNormalized(index); + imageStore(output_image, int3(int2(coords), 0), norm_color); } )"}}, // We do the inverse BT.601 conversion for YCbCr to RGB // http://www.equasys.de/colorconversion.html#YCbCr-RGBColorFormatConversion {TextureFormat::XFB, - {BUFFER_FORMAT_RGBA8_UINT, 0, 8, 8, false, + {TEXEL_BUFFER_FORMAT_RGBA8_UINT, 0, 8, 8, false, R"( - layout(local_size_x = 8, local_size_y = 8) in; - - void main() + DEFINE_MAIN(8, 8) { - uvec2 uv = gl_GlobalInvocationID.xy; + uint2 uv = gl_GlobalInvocationID.xy; int buffer_pos = int(u_src_offset + (uv.y * u_src_row_stride) + (uv.x / 2u)); - vec4 yuyv = vec4(texelFetch(s_input_buffer, buffer_pos)); + float4 yuyv = float4(texelFetch(s_input_buffer, buffer_pos)); float y = mix(yuyv.r, yuyv.b, (uv.x & 1u) == 1u); @@ -1355,33 +1351,21 @@ static const std::map<TextureFormat, DecodingShaderInfo> s_decoding_shader_info{ float uComp = yuyv.g - 128.0; float vComp = yuyv.a - 128.0; - vec4 rgb = vec4(yComp + (1.596 * vComp), + float4 rgb = float4(yComp + (1.596 * vComp), yComp - (0.813 * vComp) - (0.391 * uComp), yComp + (2.018 * uComp), 255.0); - vec4 rgba_norm = rgb / 255.0; - imageStore(output_image, ivec3(ivec2(uv), 0), rgba_norm); + float4 rgba_norm = rgb / 255.0; + imageStore(output_image, int3(int2(uv), 0), rgba_norm); } )"}}}; -static const std::array<u32, BUFFER_FORMAT_COUNT> s_buffer_bytes_per_texel = {{ - 1, // BUFFER_FORMAT_R8_UINT - 2, // BUFFER_FORMAT_R16_UINT - 8, // BUFFER_FORMAT_R32G32_UINT - 4, // BUFFER_FORMAT_RGBA8_UINT -}}; - const DecodingShaderInfo* GetDecodingShaderInfo(TextureFormat format) { auto iter = s_decoding_shader_info.find(format); return iter != s_decoding_shader_info.end() ? &iter->second : nullptr; } -u32 GetBytesPerBufferElement(BufferFormat buffer_format) -{ - return s_buffer_bytes_per_texel[buffer_format]; -} - std::pair<u32, u32> GetDispatchCount(const DecodingShaderInfo* info, u32 width, u32 height) { // Flatten to a single dimension? @@ -1419,4 +1403,126 @@ std::string GenerateDecodingShader(TextureFormat format, TLUTFormat palette_form return ss.str(); } +std::string GeneratePaletteConversionShader(TLUTFormat palette_format, APIType api_type) +{ + std::stringstream ss; + + ss << R"( +int Convert3To8(int v) +{ + // Swizzle bits: 00000123 -> 12312312 + return (v << 5) | (v << 2) | (v >> 1); +} +int Convert4To8(int v) +{ + // Swizzle bits: 00001234 -> 12341234 + return (v << 4) | v; +} +int Convert5To8(int v) +{ + // Swizzle bits: 00012345 -> 12345123 + return (v << 3) | (v >> 2); +} +int Convert6To8(int v) +{ + // Swizzle bits: 00123456 -> 12345612 + return (v << 2) | (v >> 4); +})"; + + switch (palette_format) + { + case TLUTFormat::IA8: + ss << R"( +float4 DecodePixel(int val) +{ + int i = val & 0xFF; + int a = val >> 8; + return float4(i, i, i, a) / 255.0; +})"; + break; + + case TLUTFormat::RGB565: + ss << R"( +float4 DecodePixel(int val) +{ + int r, g, b, a; + r = Convert5To8((val >> 11) & 0x1f); + g = Convert6To8((val >> 5) & 0x3f); + b = Convert5To8((val) & 0x1f); + a = 0xFF; + return float4(r, g, b, a) / 255.0; +})"; + break; + + case TLUTFormat::RGB5A3: + ss << R"( +float4 DecodePixel(int val) +{ + int r,g,b,a; + if ((val&0x8000) > 0) + { + r=Convert5To8((val>>10) & 0x1f); + g=Convert5To8((val>>5 ) & 0x1f); + b=Convert5To8((val ) & 0x1f); + a=0xFF; + } + else + { + a=Convert3To8((val>>12) & 0x7); + r=Convert4To8((val>>8 ) & 0xf); + g=Convert4To8((val>>4 ) & 0xf); + b=Convert4To8((val ) & 0xf); + } + return float4(r, g, b, a) / 255.0; +})"; + break; + + default: + PanicAlert("Unknown format"); + break; + } + + ss << "\n"; + + if (api_type == APIType::D3D) + { + ss << "Buffer<uint> tex0 : register(t0);\n"; + ss << "Texture2DArray tex1 : register(t1);\n"; + ss << "SamplerState samp1 : register(s1);\n"; + ss << "cbuffer PSBlock : register(b0) {\n"; + } + else + { + ss << "TEXEL_BUFFER_BINDING(0) uniform usamplerBuffer samp0;\n"; + ss << "SAMPLER_BINDING(1) uniform sampler2DArray samp1;\n"; + ss << "UBO_BINDING(std140, 1) uniform PSBlock {\n"; + } + + ss << " float multiplier;\n"; + ss << " int texel_buffer_offset;\n"; + ss << "};\n"; + + if (api_type == APIType::D3D) + { + ss << "void main(in float3 v_tex0 : TEXCOORD0, out float4 ocol0 : SV_Target) {\n"; + ss << " int src = int(round(tex1.Sample(samp1, v_tex0).r * multiplier));\n"; + ss << " src = int(tex0.Load(src + texel_buffer_offset).r);\n"; + } + else + { + ss << "VARYING_LOCATION(0) in float3 v_tex0;\n"; + ss << "FRAGMENT_OUTPUT_LOCATION(0) out float4 ocol0;\n"; + ss << "void main() {\n"; + ss << " float3 coords = v_tex0;\n"; + ss << " int src = int(round(texture(samp1, coords).r * multiplier));\n"; + ss << " src = int(texelFetch(samp0, src + texel_buffer_offset).r);\n"; + } + + ss << " src = ((src << 8) & 0xFF00) | (src >> 8);\n"; + ss << " ocol0 = DecodePixel(src);\n"; + ss << "}\n"; + + return ss.str(); +} + } // namespace TextureConversionShaderTiled diff --git a/Source/Core/VideoCommon/TextureConversionShader.h b/Source/Core/VideoCommon/TextureConversionShader.h index 804d59b193..f6c266bd63 100644 --- a/Source/Core/VideoCommon/TextureConversionShader.h +++ b/Source/Core/VideoCommon/TextureConversionShader.h @@ -13,6 +13,7 @@ enum class APIType; enum class TextureFormat; enum class EFBCopyFormat; enum class TLUTFormat; +enum TexelBufferFormat : u32; struct EFBCopyParams; namespace TextureConversionShaderTiled @@ -21,20 +22,10 @@ u16 GetEncodedSampleCount(EFBCopyFormat format); const char* GenerateEncodingShader(const EFBCopyParams& params, APIType ApiType); -// View format of the input data to the texture decoding shader. -enum BufferFormat -{ - BUFFER_FORMAT_R8_UINT, - BUFFER_FORMAT_R16_UINT, - BUFFER_FORMAT_R32G32_UINT, - BUFFER_FORMAT_RGBA8_UINT, - BUFFER_FORMAT_COUNT -}; - // Information required to compile and dispatch a texture decoding shader. struct DecodingShaderInfo { - BufferFormat buffer_format; + TexelBufferFormat buffer_format; u32 palette_size; u32 group_size_x; u32 group_size_y; @@ -46,10 +37,6 @@ struct DecodingShaderInfo // If this format does not have a shader written for it, returns nullptr. const DecodingShaderInfo* GetDecodingShaderInfo(TextureFormat format); -// Determine how many bytes there are in each element of the texel buffer. -// Needed for alignment and stride calculations. -u32 GetBytesPerBufferElement(BufferFormat buffer_format); - // Determine how many thread groups should be dispatched for an image of the specified width/height. // First is the number of X groups, second is the number of Y groups, Z is always one. std::pair<u32, u32> GetDispatchCount(const DecodingShaderInfo* info, u32 width, u32 height); @@ -58,4 +45,7 @@ std::pair<u32, u32> GetDispatchCount(const DecodingShaderInfo* info, u32 width, std::string GenerateDecodingShader(TextureFormat format, TLUTFormat palette_format, APIType api_type); +// Returns the GLSL string containing the palette conversion shader for the specified format. +std::string GeneratePaletteConversionShader(TLUTFormat palette_format, APIType api_type); + } // namespace TextureConversionShaderTiled diff --git a/Source/Core/VideoCommon/TextureConverterShaderGen.cpp b/Source/Core/VideoCommon/TextureConverterShaderGen.cpp index 5cfb3e7c76..15de7ba9a7 100644 --- a/Source/Core/VideoCommon/TextureConverterShaderGen.cpp +++ b/Source/Core/VideoCommon/TextureConverterShaderGen.cpp @@ -31,72 +31,99 @@ TCShaderUid GetShaderUid(EFBCopyFormat dst_format, bool is_depth_copy, bool is_i return out; } -ShaderCode GenerateShader(APIType api_type, const UidData* uid_data) +static void WriteHeader(APIType api_type, ShaderCode& out) { - const bool mono_depth = uid_data->is_depth_copy && g_ActiveConfig.bStereoEFBMonoDepth; - - ShaderCode out; - if (api_type == APIType::OpenGL) + if (api_type == APIType::D3D) { - out.Write("SAMPLER_BINDING(9) uniform sampler2DArray samp9;\n" - "uniform float3 filter_coefficients;\n" - "uniform float gamma_rcp;\n" - "uniform float2 clamp_tb;\n" - "uniform float pixel_height;\n"); - out.Write("float4 SampleEFB(float3 uv, float y_offset) {\n" - " return texture(samp9, float3(uv.x, clamp(uv.y - (y_offset * pixel_height), " - "clamp_tb.x, clamp_tb.y), %s));\n" - "}\n", - mono_depth ? "0.0" : "uv.z"); - out.Write("#define uv0 f_uv0\n" - "in vec3 uv0;\n" - "out vec4 ocol0;\n" - "void main(){\n"); + out.Write("cbuffer PSBlock : register(b0) {\n" + " float2 src_offset, src_size;\n" + " float3 filter_coefficients;\n" + " float gamma_rcp;\n" + " float2 clamp_tb;\n" + " float pixel_height;\n" + "};\n\n"); } - else if (api_type == APIType::Vulkan) + else if (api_type == APIType::OpenGL || api_type == APIType::Vulkan) { out.Write("UBO_BINDING(std140, 1) uniform PSBlock {\n" + " float2 src_offset, src_size;\n" " float3 filter_coefficients;\n" " float gamma_rcp;\n" " float2 clamp_tb;\n" " float pixel_height;\n" "};\n"); - out.Write("SAMPLER_BINDING(0) uniform sampler2DArray samp0;\n"); - out.Write("float4 SampleEFB(float3 uv, float y_offset) {\n" - " return texture(samp0, float3(uv.x, clamp(uv.y + (y_offset * pixel_height), " - "clamp_tb.x, clamp_tb.y), %s));\n" - "}\n", - mono_depth ? "0.0" : "uv.z"); - out.Write("layout(location = 0) in vec3 uv0;\n" - "layout(location = 1) in vec4 col0;\n" - "layout(location = 0) out vec4 ocol0;" - "void main(){\n"); } - else if (api_type == APIType::D3D) +} + +ShaderCode GenerateVertexShader(APIType api_type) +{ + ShaderCode out; + WriteHeader(api_type, out); + + if (api_type == APIType::D3D) + { + out.Write("void main(in uint id : SV_VertexID, out float3 v_tex0 : TEXCOORD0,\n" + " out float4 opos : SV_Position) {\n"); + } + else if (api_type == APIType::OpenGL || api_type == APIType::Vulkan) + { + out.Write("VARYING_LOCATION(0) out float3 v_tex0;\n" + "#define id gl_VertexID\n" + "#define opos gl_Position\n" + "void main() {\n"); + } + out.Write(" v_tex0 = float3(float((id << 1) & 2), float(id & 2), 0.0f);\n"); + out.Write( + " opos = float4(v_tex0.xy * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);\n"); + out.Write(" v_tex0 = float3(src_offset + (src_size * v_tex0.xy), 0.0f);\n"); + + // NDC space is flipped in Vulkan + if (api_type == APIType::Vulkan) + out.Write(" opos.y = -opos.y;\n"); + + out.Write("}\n"); + + return out; +} + +ShaderCode GeneratePixelShader(APIType api_type, const UidData* uid_data) +{ + const bool mono_depth = uid_data->is_depth_copy && g_ActiveConfig.bStereoEFBMonoDepth; + + ShaderCode out; + WriteHeader(api_type, out); + + if (api_type == APIType::D3D) { out.Write("Texture2DArray tex0 : register(t0);\n" "SamplerState samp0 : register(s0);\n" - "uniform float3 filter_coefficients;\n" - "uniform float gamma_rcp;\n" - "uniform float2 clamp_tb;\n" - "uniform float pixel_height;\n\n"); - out.Write("float4 SampleEFB(float3 uv, float y_offset) {\n" + "float4 SampleEFB(float3 uv, float y_offset) {\n" " return tex0.Sample(samp0, float3(uv.x, clamp(uv.y + (y_offset * pixel_height), " "clamp_tb.x, clamp_tb.y), %s));\n" + "}\n\n", + mono_depth ? "0.0" : "uv.z"); + out.Write("void main(in float3 v_tex0 : TEXCOORD0, out float4 ocol0 : SV_Target)\n{\n"); + } + else if (api_type == APIType::OpenGL || api_type == APIType::Vulkan) + { + out.Write("SAMPLER_BINDING(0) uniform sampler2DArray samp0;\n"); + out.Write("float4 SampleEFB(float3 uv, float y_offset) {\n" + " return texture(samp0, float3(uv.x, clamp(uv.y + (y_offset * pixel_height), " + "clamp_tb.x, clamp_tb.y), %s));\n" "}\n", mono_depth ? "0.0" : "uv.z"); - out.Write("void main(out float4 ocol0 : SV_Target,\n" - " in float4 pos : SV_Position,\n" - " in float3 uv0 : TEXCOORD0) {\n"); + out.Write("VARYING_LOCATION(0) in vec3 v_tex0;\n" + "FRAGMENT_OUTPUT_LOCATION(0) out vec4 ocol0;" + "void main()\n{\n"); } // The copy filter applies to both color and depth copies. This has been verified on hardware. // The filter is only applied to the RGB channels, the alpha channel is left intact. if (uid_data->copy_filter) { - out.Write(" float4 prev_row = SampleEFB(uv0, -1.0f);\n" - " float4 current_row = SampleEFB(uv0, 0.0f);\n" - " float4 next_row = SampleEFB(uv0, 1.0f);\n" + out.Write(" float4 prev_row = SampleEFB(v_tex0, -1.0f);\n" + " float4 current_row = SampleEFB(v_tex0, 0.0f);\n" + " float4 next_row = SampleEFB(v_tex0, 1.0f);\n" " float4 texcol = float4(min(prev_row.rgb * filter_coefficients[0] +\n" " current_row.rgb * filter_coefficients[1] +\n" " next_row.rgb * filter_coefficients[2], \n" @@ -105,14 +132,14 @@ ShaderCode GenerateShader(APIType api_type, const UidData* uid_data) else { out.Write( - " float4 current_row = SampleEFB(uv0, 0.0f);\n" + " float4 current_row = SampleEFB(v_tex0, 0.0f);\n" " float4 texcol = float4(min(current_row.rgb * filter_coefficients[1], float3(1, 1, 1)),\n" " current_row.a);\n"); } if (uid_data->is_depth_copy) { - if (api_type == APIType::D3D || api_type == APIType::Vulkan) + if (!g_ActiveConfig.backend_info.bSupportsReversedDepthRange) out.Write("texcol.x = 1.0 - texcol.x;\n"); out.Write(" int depth = int(texcol.x * 16777216.0);\n" @@ -268,8 +295,8 @@ ShaderCode GenerateShader(APIType api_type, const UidData* uid_data) break; case EFBCopyFormat::XFB: - out.Write(" ocol0 = float4(pow(texcol.rgb, float3(gamma_rcp, gamma_rcp, gamma_rcp)), " - "texcol.a);\n"); + out.Write( + " ocol0 = float4(pow(texcol.rgb, float3(gamma_rcp, gamma_rcp, gamma_rcp)), 1.0f);\n"); break; default: diff --git a/Source/Core/VideoCommon/TextureConverterShaderGen.h b/Source/Core/VideoCommon/TextureConverterShaderGen.h index 1f231f53c3..af8e7af0bc 100644 --- a/Source/Core/VideoCommon/TextureConverterShaderGen.h +++ b/Source/Core/VideoCommon/TextureConverterShaderGen.h @@ -28,7 +28,8 @@ struct UidData using TCShaderUid = ShaderUid<UidData>; -ShaderCode GenerateShader(APIType api_type, const UidData* uid_data); +ShaderCode GenerateVertexShader(APIType api_type); +ShaderCode GeneratePixelShader(APIType api_type, const UidData* uid_data); TCShaderUid GetShaderUid(EFBCopyFormat dst_format, bool is_depth_copy, bool is_intensity, bool scale_by_half, bool copy_filter); diff --git a/Source/Core/VideoCommon/UberShaderPixel.cpp b/Source/Core/VideoCommon/UberShaderPixel.cpp index 27e69808dc..1b9695f5ce 100644 --- a/Source/Core/VideoCommon/UberShaderPixel.cpp +++ b/Source/Core/VideoCommon/UberShaderPixel.cpp @@ -52,8 +52,7 @@ ShaderCode GenPixelShader(APIType ApiType, const ShaderHostConfig& host_config, const bool use_shader_blend = !use_dual_source && host_config.backend_shader_framebuffer_fetch; const bool early_depth = uid_data->early_depth != 0; const bool per_pixel_depth = uid_data->per_pixel_depth != 0; - const bool bounding_box = - host_config.bounding_box && g_ActiveConfig.BBoxUseFragmentShaderImplementation(); + const bool bounding_box = host_config.bounding_box; const u32 numTexgen = uid_data->num_texgens; ShaderCode out; @@ -1058,7 +1057,7 @@ ShaderCode GenPixelShader(APIType ApiType, const ShaderHostConfig& host_config, if (host_config.fast_depth_calc) { - if (ApiType == APIType::D3D || ApiType == APIType::Vulkan) + if (!host_config.backend_reversed_depth_range) out.Write(" int zCoord = int((1.0 - rawpos.z) * 16777216.0);\n"); else out.Write(" int zCoord = int(rawpos.z * 16777216.0);\n"); @@ -1113,7 +1112,7 @@ ShaderCode GenPixelShader(APIType ApiType, const ShaderHostConfig& host_config, out.Write(" // If early depth is enabled, write to zbuffer before depth textures\n"); out.Write(" // If early depth isn't enabled, we write to the zbuffer here\n"); out.Write(" int zbuffer_zCoord = bpmem_late_ztest ? zCoord : early_zCoord;\n"); - if (ApiType == APIType::D3D || ApiType == APIType::Vulkan) + if (!host_config.backend_reversed_depth_range) out.Write(" depth = 1.0 - float(zbuffer_zCoord) / 16777216.0;\n"); else out.Write(" depth = float(zbuffer_zCoord) / 16777216.0;\n"); diff --git a/Source/Core/VideoCommon/VertexLoaderManager.cpp b/Source/Core/VideoCommon/VertexLoaderManager.cpp index 9cb7d23089..673569f4d9 100644 --- a/Source/Core/VideoCommon/VertexLoaderManager.cpp +++ b/Source/Core/VideoCommon/VertexLoaderManager.cpp @@ -19,6 +19,7 @@ #include "VideoCommon/DataReader.h" #include "VideoCommon/IndexGenerator.h" #include "VideoCommon/NativeVertexFormat.h" +#include "VideoCommon/RenderBase.h" #include "VideoCommon/Statistics.h" #include "VideoCommon/VertexLoaderBase.h" #include "VideoCommon/VertexLoaderManager.h" @@ -131,7 +132,7 @@ NativeVertexFormat* GetOrCreateMatchingFormat(const PortableVertexDeclaration& d auto iter = s_native_vertex_map.find(decl); if (iter == s_native_vertex_map.end()) { - std::unique_ptr<NativeVertexFormat> fmt = g_vertex_manager->CreateNativeVertexFormat(decl); + std::unique_ptr<NativeVertexFormat> fmt = g_renderer->CreateNativeVertexFormat(decl); auto ipair = s_native_vertex_map.emplace(decl, std::move(fmt)); iter = ipair.first; } @@ -228,9 +229,7 @@ static VertexLoaderBase* RefreshLoader(int vtx_attr_group, bool preprocess = fal const PortableVertexDeclaration& format = loader->m_native_vtx_decl; std::unique_ptr<NativeVertexFormat>& native = s_native_vertex_map[format]; if (!native) - { - native = g_vertex_manager->CreateNativeVertexFormat(format); - } + native = g_renderer->CreateNativeVertexFormat(format); loader->m_native_vertex_format = native.get(); } state->vertex_loaders[vtx_attr_group] = loader; diff --git a/Source/Core/VideoCommon/VertexManagerBase.cpp b/Source/Core/VideoCommon/VertexManagerBase.cpp index 0e178e4d9b..bbc355a98c 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.cpp +++ b/Source/Core/VideoCommon/VertexManagerBase.cpp @@ -17,8 +17,9 @@ #include "Core/ConfigManager.h" #include "VideoCommon/BPMemory.h" +#include "VideoCommon/BoundingBox.h" #include "VideoCommon/DataReader.h" -#include "VideoCommon/Debugger.h" +#include "VideoCommon/FramebufferManager.h" #include "VideoCommon/GeometryShaderManager.h" #include "VideoCommon/IndexGenerator.h" #include "VideoCommon/NativeVertexFormat.h" @@ -79,11 +80,15 @@ static bool AspectIs16_9(float width, float height) } VertexManagerBase::VertexManagerBase() + : m_cpu_vertex_buffer(MAXVBUFFERSIZE), m_cpu_index_buffer(MAXIBUFFERSIZE) { } -VertexManagerBase::~VertexManagerBase() +VertexManagerBase::~VertexManagerBase() = default; + +bool VertexManagerBase::Initialize() { + return true; } u32 VertexManagerBase::GetRemainingSize() const @@ -94,6 +99,10 @@ u32 VertexManagerBase::GetRemainingSize() const DataReader VertexManagerBase::PrepareForAdditionalData(int primitive, u32 count, u32 stride, bool cullall) { + // Flush all EFB pokes and invalidate the peek cache. + g_framebuffer_manager->InvalidatePeekCache(); + g_framebuffer_manager->FlushEFBPokes(); + // The SSE vertex loader can write up to 4 bytes past the end u32 const needed_vertex_bytes = count * stride + 4; @@ -132,7 +141,18 @@ DataReader VertexManagerBase::PrepareForAdditionalData(int primitive, u32 count, // need to alloc new buffer if (m_is_flushed) { - g_vertex_manager->ResetBuffer(stride, cullall); + if (cullall) + { + // This buffer isn't getting sent to the GPU. Just allocate it on the cpu. + m_cur_buffer_pointer = m_base_buffer_pointer = m_cpu_vertex_buffer.data(); + m_end_buffer_pointer = m_base_buffer_pointer + m_cpu_vertex_buffer.size(); + IndexGenerator::Start(m_cpu_index_buffer.data()); + } + else + { + ResetBuffer(stride); + } + m_is_flushed = false; } @@ -210,6 +230,48 @@ std::pair<size_t, size_t> VertexManagerBase::ResetFlushAspectRatioCount() return val; } +void VertexManagerBase::ResetBuffer(u32 vertex_stride) +{ + m_base_buffer_pointer = m_cpu_vertex_buffer.data(); + m_cur_buffer_pointer = m_cpu_vertex_buffer.data(); + m_end_buffer_pointer = m_base_buffer_pointer + m_cpu_vertex_buffer.size(); + IndexGenerator::Start(m_cpu_index_buffer.data()); +} + +void VertexManagerBase::CommitBuffer(u32 num_vertices, u32 vertex_stride, u32 num_indices, + u32* out_base_vertex, u32* out_base_index) +{ + *out_base_vertex = 0; + *out_base_index = 0; +} + +void VertexManagerBase::DrawCurrentBatch(u32 base_index, u32 num_indices, u32 base_vertex) +{ + // If bounding box is enabled, we need to flush any changes first, then invalidate what we have. + if (::BoundingBox::active && g_ActiveConfig.bBBoxEnable && + g_ActiveConfig.backend_info.bSupportsBBox) + { + g_renderer->BBoxFlush(); + } + + g_renderer->DrawIndexed(base_index, num_indices, base_vertex); +} + +void VertexManagerBase::UploadUniforms() +{ +} + +void VertexManagerBase::InvalidateConstants() +{ + VertexShaderManager::dirty = true; + GeometryShaderManager::dirty = true; + PixelShaderManager::dirty = true; +} + +void VertexManagerBase::UploadUtilityUniforms(const void* uniforms, u32 uniforms_size) +{ +} + void VertexManagerBase::UploadUtilityVertices(const void* vertices, u32 vertex_stride, u32 num_vertices, const u16* indices, u32 num_indices, u32* out_base_vertex, u32* out_base_index) @@ -218,7 +280,7 @@ void VertexManagerBase::UploadUtilityVertices(const void* vertices, u32 vertex_s ASSERT(m_is_flushed); // Copy into the buffers usually used for GX drawing. - ResetBuffer(std::max(vertex_stride, 1u), false); + ResetBuffer(std::max(vertex_stride, 1u)); if (vertices) { const u32 copy_size = vertex_stride * num_vertices; @@ -232,64 +294,42 @@ void VertexManagerBase::UploadUtilityVertices(const void* vertices, u32 vertex_s CommitBuffer(num_vertices, vertex_stride, num_indices, out_base_vertex, out_base_index); } -static void SetSamplerState(u32 index, float custom_tex_scale, bool custom_tex, - bool has_arbitrary_mips) +u32 VertexManagerBase::GetTexelBufferElementSize(TexelBufferFormat buffer_format) { - const FourTexUnits& tex = bpmem.tex[index / 4]; - const TexMode0& tm0 = tex.texMode0[index % 4]; - - SamplerState state = {}; - state.Generate(bpmem, index); - - // Force texture filtering config option. - if (g_ActiveConfig.bForceFiltering) - { - state.min_filter = SamplerState::Filter::Linear; - state.mag_filter = SamplerState::Filter::Linear; - state.mipmap_filter = SamplerCommon::AreBpTexMode0MipmapsEnabled(tm0) ? - SamplerState::Filter::Linear : - SamplerState::Filter::Point; - } - - // Custom textures may have a greater number of mips - if (custom_tex) - state.max_lod = 255; - - // Anisotropic filtering option. - if (g_ActiveConfig.iMaxAnisotropy != 0 && !SamplerCommon::IsBpTexMode0PointFiltering(tm0)) - { - // https://www.opengl.org/registry/specs/EXT/texture_filter_anisotropic.txt - // For predictable results on all hardware/drivers, only use one of: - // GL_LINEAR + GL_LINEAR (No Mipmaps [Bilinear]) - // GL_LINEAR + GL_LINEAR_MIPMAP_LINEAR (w/ Mipmaps [Trilinear]) - // Letting the game set other combinations will have varying arbitrary results; - // possibly being interpreted as equal to bilinear/trilinear, implicitly - // disabling anisotropy, or changing the anisotropic algorithm employed. - state.min_filter = SamplerState::Filter::Linear; - state.mag_filter = SamplerState::Filter::Linear; - if (SamplerCommon::AreBpTexMode0MipmapsEnabled(tm0)) - state.mipmap_filter = SamplerState::Filter::Linear; - state.anisotropic_filtering = 1; - } - else - { - state.anisotropic_filtering = 0; - } + // R8 - 1, R16 - 2, RGBA8 - 4, R32G32 - 8 + return 1u << static_cast<u32>(buffer_format); +} - if (has_arbitrary_mips && SamplerCommon::AreBpTexMode0MipmapsEnabled(tm0)) - { - // Apply a secondary bias calculated from the IR scale to pull inwards mipmaps - // that have arbitrary contents, eg. are used for fog effects where the - // distance they kick in at is important to preserve at any resolution. - // Correct this with the upscaling factor of custom textures. - s64 lod_offset = std::log2(g_renderer->GetEFBScale() / custom_tex_scale) * 256.f; - state.lod_bias = MathUtil::Clamp<s64>(state.lod_bias + lod_offset, -32768, 32767); +bool VertexManagerBase::UploadTexelBuffer(const void* data, u32 data_size, TexelBufferFormat format, + u32* out_offset) +{ + return false; +} - // Anisotropic also pushes mips farther away so it cannot be used either - state.anisotropic_filtering = 0; - } +bool VertexManagerBase::UploadTexelBuffer(const void* data, u32 data_size, TexelBufferFormat format, + u32* out_offset, const void* palette_data, + u32 palette_size, TexelBufferFormat palette_format, + u32* palette_offset) +{ + return false; +} + +void VertexManagerBase::LoadTextures() +{ + BitSet32 usedtextures; + for (u32 i = 0; i < bpmem.genMode.numtevstages + 1u; ++i) + if (bpmem.tevorders[i / 2].getEnable(i & 1)) + usedtextures[bpmem.tevorders[i / 2].getTexMap(i & 1)] = true; + + if (bpmem.genMode.numindstages > 0) + for (unsigned int i = 0; i < bpmem.genMode.numtevstages + 1u; ++i) + if (bpmem.tevind[i].IsActive() && bpmem.tevind[i].bt < bpmem.genMode.numindstages) + usedtextures[bpmem.tevindref.getTexMap(bpmem.tevind[i].bt)] = true; - g_renderer->SetSamplerState(index, state); + for (unsigned int i : usedtextures) + g_texture_cache->Load(i); + + g_texture_cache->BindTextures(); } void VertexManagerBase::Flush() @@ -297,6 +337,8 @@ void VertexManagerBase::Flush() if (m_is_flushed) return; + m_is_flushed = true; + // loading a state will invalidate BP, so check for it g_video_backend->CheckInvalidState(); @@ -340,41 +382,6 @@ void VertexManagerBase::Flush() (bpmem.alpha_test.hex >> 16) & 0xff); #endif - // If the primitave is marked CullAll. All we need to do is update the vertex constants and - // calculate the zfreeze refrence slope - if (!m_cull_all) - { - BitSet32 usedtextures; - for (u32 i = 0; i < bpmem.genMode.numtevstages + 1u; ++i) - if (bpmem.tevorders[i / 2].getEnable(i & 1)) - usedtextures[bpmem.tevorders[i / 2].getTexMap(i & 1)] = true; - - if (bpmem.genMode.numindstages > 0) - for (unsigned int i = 0; i < bpmem.genMode.numtevstages + 1u; ++i) - if (bpmem.tevind[i].IsActive() && bpmem.tevind[i].bt < bpmem.genMode.numindstages) - usedtextures[bpmem.tevindref.getTexMap(bpmem.tevind[i].bt)] = true; - - for (unsigned int i : usedtextures) - { - const auto* tentry = g_texture_cache->Load(i); - - if (tentry) - { - float custom_tex_scale = tentry->GetWidth() / float(tentry->native_width); - SetSamplerState(i, custom_tex_scale, tentry->is_custom_tex, tentry->has_arbitrary_mips); - PixelShaderManager::SetTexDims(i, tentry->native_width, tentry->native_height); - } - else - { - ERROR_LOG(VIDEO, "error loading texture"); - } - } - g_texture_cache->BindTextures(); - } - - // set global vertex constants - VertexShaderManager::SetConstants(); - // Track some stats used elsewhere by the anamorphic widescreen heuristic. if (!SConfig::GetInstance().bWii) { @@ -394,6 +401,7 @@ void VertexManagerBase::Flush() } // Calculate ZSlope for zfreeze + VertexShaderManager::SetConstants(); if (!bpmem.genMode.zfreeze) { // Must be done after VertexShaderManager::SetConstants() @@ -407,20 +415,24 @@ void VertexManagerBase::Flush() if (!m_cull_all) { - // Update and upload constants. Note for the Vulkan backend, this must occur before the - // vertex/index buffer is committed, otherwise the data will be associated with the - // previous command buffer, instead of the one with the draw if there is an overflow. - GeometryShaderManager::SetConstants(); - PixelShaderManager::SetConstants(); - UploadConstants(); - - // Now the vertices can be flushed to the GPU. + // Now the vertices can be flushed to the GPU. Everything following the CommitBuffer() call + // must be careful to not upload any utility vertices, as the binding will be lost otherwise. const u32 num_indices = IndexGenerator::GetIndexLen(); u32 base_vertex, base_index; CommitBuffer(IndexGenerator::GetNumVerts(), VertexLoaderManager::GetCurrentVertexFormat()->GetVertexStride(), num_indices, &base_vertex, &base_index); + // Texture loading can cause palettes to be applied (-> uniforms -> draws). + // Palette application does not use vertices, only a full-screen quad, so this is okay. + // Same with GPU texture decoding, which uses compute shaders. + LoadTextures(); + + // Now we can upload uniforms, as nothing else will override them. + GeometryShaderManager::SetConstants(); + PixelShaderManager::SetConstants(); + UploadUniforms(); + // Update the pipeline, or compile one if needed. UpdatePipelineConfig(); UpdatePipelineObject(); @@ -435,18 +447,17 @@ void VertexManagerBase::Flush() if (PerfQueryBase::ShouldEmulate()) g_perf_query->DisableQuery(bpmem.zcontrol.early_ztest ? PQG_ZCOMP_ZCOMPLOC : PQG_ZCOMP); + + OnDraw(); } } - GFX_DEBUGGER_PAUSE_AT(NEXT_FLUSH, true); - if (xfmem.numTexGen.numTexGens != bpmem.genMode.numtexgens) + { ERROR_LOG(VIDEO, "xf.numtexgens (%d) does not match bp.numtexgens (%d). Error in command stream.", xfmem.numTexGen.numTexGens, bpmem.genMode.numtexgens.Value()); - - m_is_flushed = true; - m_cull_all = false; + } } void VertexManagerBase::DoState(PointerWrap& p) @@ -649,3 +660,109 @@ void VertexManagerBase::UpdatePipelineObject() break; } } + +void VertexManagerBase::OnDraw() +{ + m_draw_counter++; + + // If we didn't have any CPU access last frame, do nothing. + if (m_scheduled_command_buffer_kicks.empty() || !m_allow_background_execution) + return; + + // Check if this draw is scheduled to kick a command buffer. + // The draw counters will always be sorted so a binary search is possible here. + if (std::binary_search(m_scheduled_command_buffer_kicks.begin(), + m_scheduled_command_buffer_kicks.end(), m_draw_counter)) + { + // Kick a command buffer on the background thread. + g_renderer->Flush(); + } +} + +void VertexManagerBase::OnCPUEFBAccess() +{ + // Check this isn't another access without any draws inbetween. + if (!m_cpu_accesses_this_frame.empty() && m_cpu_accesses_this_frame.back() == m_draw_counter) + return; + + // Store the current draw counter for scheduling in OnEndFrame. + m_cpu_accesses_this_frame.emplace_back(m_draw_counter); +} + +void VertexManagerBase::OnEFBCopyToRAM() +{ + // If we're not deferring, try to preempt it next frame. + if (!g_ActiveConfig.bDeferEFBCopies) + { + OnCPUEFBAccess(); + return; + } + + // Otherwise, only execute if we have at least 10 objects between us and the last copy. + const u32 diff = m_draw_counter - m_last_efb_copy_draw_counter; + m_last_efb_copy_draw_counter = m_draw_counter; + if (diff < MINIMUM_DRAW_CALLS_PER_COMMAND_BUFFER_FOR_READBACK) + return; + + g_renderer->Flush(); +} + +void VertexManagerBase::OnEndFrame() +{ + m_draw_counter = 0; + m_last_efb_copy_draw_counter = 0; + m_scheduled_command_buffer_kicks.clear(); + + // If we have no CPU access at all, leave everything in the one command buffer for maximum + // parallelism between CPU/GPU, at the cost of slightly higher latency. + if (m_cpu_accesses_this_frame.empty()) + return; + + // In order to reduce CPU readback latency, we want to kick a command buffer roughly halfway + // between the draw counters that invoked the readback, or every 250 draws, whichever is smaller. + if (g_ActiveConfig.iCommandBufferExecuteInterval > 0) + { + u32 last_draw_counter = 0; + u32 interval = static_cast<u32>(g_ActiveConfig.iCommandBufferExecuteInterval); + for (u32 draw_counter : m_cpu_accesses_this_frame) + { + // We don't want to waste executing command buffers for only a few draws, so set a minimum. + // Leave last_draw_counter as-is, so we get the correct number of draws between submissions. + u32 draw_count = draw_counter - last_draw_counter; + if (draw_count < MINIMUM_DRAW_CALLS_PER_COMMAND_BUFFER_FOR_READBACK) + continue; + + if (draw_count <= interval) + { + u32 mid_point = draw_count / 2; + m_scheduled_command_buffer_kicks.emplace_back(last_draw_counter + mid_point); + } + else + { + u32 counter = interval; + while (counter < draw_count) + { + m_scheduled_command_buffer_kicks.emplace_back(last_draw_counter + counter); + counter += interval; + } + } + + last_draw_counter = draw_counter; + } + } + +#if 0 + { + std::stringstream ss; + std::for_each(m_cpu_accesses_this_frame.begin(), m_cpu_accesses_this_frame.end(), [&ss](u32 idx) { ss << idx << ","; }); + WARN_LOG(VIDEO, "CPU EFB accesses in last frame: %s", ss.str().c_str()); + } + { + std::stringstream ss; + std::for_each(m_scheduled_command_buffer_kicks.begin(), m_scheduled_command_buffer_kicks.end(), [&ss](u32 idx) { ss << idx << ","; }); + WARN_LOG(VIDEO, "Scheduled command buffer kicks: %s", ss.str().c_str()); + } +#endif + + m_cpu_accesses_this_frame.clear(); +} diff --git a/Source/Core/VideoCommon/VertexManagerBase.h b/Source/Core/VideoCommon/VertexManagerBase.h index cd3e4ed552..9a657bd7f3 100644 --- a/Source/Core/VideoCommon/VertexManagerBase.h +++ b/Source/Core/VideoCommon/VertexManagerBase.h @@ -25,6 +25,16 @@ struct Slope bool dirty; }; +// View format of the input data to the texture decoding shader. +enum TexelBufferFormat : u32 +{ + TEXEL_BUFFER_FORMAT_R8_UINT, + TEXEL_BUFFER_FORMAT_R16_UINT, + TEXEL_BUFFER_FORMAT_RGBA8_UINT, + TEXEL_BUFFER_FORMAT_R32G32_UINT, + NUM_TEXEL_BUFFER_FORMATS +}; + class VertexManagerBase { private: @@ -42,19 +52,24 @@ public: // We may convert triangle-fans to triangle-lists, almost 3x as many indices. static constexpr u32 MAXIBUFFERSIZE = MathUtil::NextPowerOf2(MAX_PRIMITIVES_PER_COMMAND * 3); + // Streaming buffer sizes. + // Texel buffer will fit the maximum size of an encoded GX texture. 1024x1024, RGBA8 = 4MB. + static constexpr u32 VERTEX_STREAM_BUFFER_SIZE = 40 * 1024 * 1024; + static constexpr u32 INDEX_STREAM_BUFFER_SIZE = 4 * 1024 * 1024; + static constexpr u32 UNIFORM_STREAM_BUFFER_SIZE = 16 * 1024 * 1024; + static constexpr u32 TEXEL_STREAM_BUFFER_SIZE = 16 * 1024 * 1024; + VertexManagerBase(); - // needs to be virtual for DX11's dtor virtual ~VertexManagerBase(); + virtual bool Initialize(); + PrimitiveType GetCurrentPrimitiveType() const { return m_current_primitive_type; } DataReader PrepareForAdditionalData(int primitive, u32 count, u32 stride, bool cullall); void FlushData(u32 count, u32 stride); void Flush(); - virtual std::unique_ptr<NativeVertexFormat> - CreateNativeVertexFormat(const PortableVertexDeclaration& vtx_decl) = 0; - void DoState(PointerWrap& p); std::pair<size_t, size_t> ResetFlushAspectRatioCount(); @@ -70,38 +85,69 @@ public: } // Utility pipeline drawing (e.g. EFB copies, post-processing, UI). - virtual void UploadUtilityUniforms(const void* uniforms, u32 uniforms_size) = 0; + virtual void UploadUtilityUniforms(const void* uniforms, u32 uniforms_size); void UploadUtilityVertices(const void* vertices, u32 vertex_stride, u32 num_vertices, const u16* indices, u32 num_indices, u32* out_base_vertex, u32* out_base_index); + // Determine how many bytes there are in each element of the texel buffer. + // Needed for alignment and stride calculations. + static u32 GetTexelBufferElementSize(TexelBufferFormat buffer_format); + + // Texel buffer, used for palette conversion. + virtual bool UploadTexelBuffer(const void* data, u32 data_size, TexelBufferFormat format, + u32* out_offset); + + // The second set of parameters uploads a second blob in the same buffer, used for GPU texture + // decoding for palette textures, as both the texture data and palette must be uploaded. + virtual bool UploadTexelBuffer(const void* data, u32 data_size, TexelBufferFormat format, + u32* out_offset, const void* palette_data, u32 palette_size, + TexelBufferFormat palette_format, u32* out_palette_offset); + + // CPU access tracking - call after a draw call is made. + void OnDraw(); + + // Call after CPU access is requested. + void OnCPUEFBAccess(); + + // Call after an EFB copy to RAM. If true, the current command buffer should be executed. + void OnEFBCopyToRAM(); + + // Call at the end of a frame. + void OnEndFrame(); + protected: - // Vertex buffers/index buffer creation. - virtual void CreateDeviceObjects() {} - virtual void DestroyDeviceObjects() {} + // When utility uniforms are used, the GX uniforms need to be re-written afterwards. + static void InvalidateConstants(); // Prepares the buffer for the next batch of vertices. - virtual void ResetBuffer(u32 vertex_stride, bool cull_all) = 0; + virtual void ResetBuffer(u32 vertex_stride); // Commits/uploads the current batch of vertices. virtual void CommitBuffer(u32 num_vertices, u32 vertex_stride, u32 num_indices, - u32* out_base_vertex, u32* out_base_index) = 0; + u32* out_base_vertex, u32* out_base_index); // Uploads uniform buffers for GX draws. - virtual void UploadConstants() = 0; + virtual void UploadUniforms(); // Issues the draw call for the current batch in the backend. - virtual void DrawCurrentBatch(u32 base_index, u32 num_indices, u32 base_vertex) = 0; + virtual void DrawCurrentBatch(u32 base_index, u32 num_indices, u32 base_vertex); + + u32 GetRemainingSize() const; + static u32 GetRemainingIndices(int primitive); + + void CalculateZSlope(NativeVertexFormat* format); + void LoadTextures(); u8* m_cur_buffer_pointer = nullptr; u8* m_base_buffer_pointer = nullptr; u8* m_end_buffer_pointer = nullptr; - u32 GetRemainingSize() const; - static u32 GetRemainingIndices(int primitive); + // Alternative buffers in CPU memory for primitives we are going to discard. + std::vector<u8> m_cpu_vertex_buffer; + std::vector<u16> m_cpu_index_buffer; Slope m_zslope = {}; - void CalculateZSlope(NativeVertexFormat* format); VideoCommon::GXPipelineUid m_current_pipeline_config; VideoCommon::GXUberPipelineUid m_current_uber_pipeline_config; @@ -114,12 +160,22 @@ protected: bool m_cull_all = false; private: + // Minimum number of draws per command buffer when attempting to preempt a readback operation. + static constexpr u32 MINIMUM_DRAW_CALLS_PER_COMMAND_BUFFER_FOR_READBACK = 10; + + void UpdatePipelineConfig(); + void UpdatePipelineObject(); + bool m_is_flushed = true; size_t m_flush_count_4_3 = 0; size_t m_flush_count_anamorphic = 0; - void UpdatePipelineConfig(); - void UpdatePipelineObject(); + // CPU access tracking + u32 m_draw_counter = 0; + u32 m_last_efb_copy_draw_counter = 0; + std::vector<u32> m_cpu_accesses_this_frame; + std::vector<u32> m_scheduled_command_buffer_kicks; + bool m_allow_background_execution = true; }; extern std::unique_ptr<VertexManagerBase> g_vertex_manager; diff --git a/Source/Core/VideoCommon/VideoBackendBase.cpp b/Source/Core/VideoCommon/VideoBackendBase.cpp index aa8b3d110f..268a3b70b7 100644 --- a/Source/Core/VideoCommon/VideoBackendBase.cpp +++ b/Source/Core/VideoCommon/VideoBackendBase.cpp @@ -282,7 +282,6 @@ void VideoBackendBase::InitializeShared() m_initialized = true; m_invalid = false; - frameCount = 0; CommandProcessor::Init(); Fifo::Init(); diff --git a/Source/Core/VideoCommon/VideoCommon.vcxproj b/Source/Core/VideoCommon/VideoCommon.vcxproj index e2a8d60b1b..f4ab78c032 100644 --- a/Source/Core/VideoCommon/VideoCommon.vcxproj +++ b/Source/Core/VideoCommon/VideoCommon.vcxproj @@ -48,11 +48,11 @@ <ClCompile Include="BPStructs.cpp" /> <ClCompile Include="CommandProcessor.cpp" /> <ClCompile Include="CPMemory.cpp" /> - <ClCompile Include="Debugger.cpp" /> <ClCompile Include="DriverDetails.cpp" /> <ClCompile Include="Fifo.cpp" /> <ClCompile Include="FPSCounter.cpp" /> - <ClCompile Include="FramebufferManagerBase.cpp" /> + <ClCompile Include="FramebufferManager.cpp" /> + <ClCompile Include="FramebufferShaderGen.cpp" /> <ClCompile Include="HiresTextures.cpp" /> <ClCompile Include="HiresTextures_DDSLoader.cpp" /> <ClCompile Include="ImageWrite.cpp" /> @@ -114,11 +114,11 @@ <ClInclude Include="CommandProcessor.h" /> <ClInclude Include="CPMemory.h" /> <ClInclude Include="DataReader.h" /> - <ClInclude Include="Debugger.h" /> <ClInclude Include="DriverDetails.h" /> <ClInclude Include="Fifo.h" /> <ClInclude Include="FPSCounter.h" /> - <ClInclude Include="FramebufferManagerBase.h" /> + <ClInclude Include="FramebufferManager.h" /> + <ClInclude Include="FramebufferShaderGen.h" /> <ClInclude Include="GXPipelineTypes.h" /> <ClInclude Include="ShaderCache.h" /> <ClInclude Include="UberShaderCommon.h" /> diff --git a/Source/Core/VideoCommon/VideoCommon.vcxproj.filters b/Source/Core/VideoCommon/VideoCommon.vcxproj.filters index 3ce3543380..f804839f54 100644 --- a/Source/Core/VideoCommon/VideoCommon.vcxproj.filters +++ b/Source/Core/VideoCommon/VideoCommon.vcxproj.filters @@ -29,12 +29,6 @@ <ClCompile Include="PixelEngine.cpp" /> <ClCompile Include="VideoBackendBase.cpp" /> <ClCompile Include="VideoConfig.cpp" /> - <ClCompile Include="Debugger.cpp"> - <Filter>Base</Filter> - </ClCompile> - <ClCompile Include="FramebufferManagerBase.cpp"> - <Filter>Base</Filter> - </ClCompile> <ClCompile Include="PerfQueryBase.cpp"> <Filter>Base</Filter> </ClCompile> @@ -197,6 +191,12 @@ <ClCompile Include="ShaderCache.cpp"> <Filter>Shader Generators</Filter> </ClCompile> + <ClCompile Include="FramebufferShaderGen.cpp"> + <Filter>Shader Generators</Filter> + </ClCompile> + <ClCompile Include="FramebufferManager.cpp"> + <Filter>Base</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="CommandProcessor.h" /> @@ -206,12 +206,6 @@ <ClInclude Include="VideoBackendBase.h" /> <ClInclude Include="VideoCommon.h" /> <ClInclude Include="VideoConfig.h" /> - <ClInclude Include="Debugger.h"> - <Filter>Base</Filter> - </ClInclude> - <ClInclude Include="FramebufferManagerBase.h"> - <Filter>Base</Filter> - </ClInclude> <ClInclude Include="PerfQueryBase.h"> <Filter>Base</Filter> </ClInclude> @@ -374,7 +368,6 @@ <ClInclude Include="AbstractPipeline.h"> <Filter>Base</Filter> </ClInclude> - <ClInclude Include="TextureConverterShaderGen.h" /> <ClInclude Include="AbstractFramebuffer.h"> <Filter>Base</Filter> </ClInclude> @@ -384,6 +377,15 @@ <ClInclude Include="ShaderCache.h"> <Filter>Shader Generators</Filter> </ClInclude> + <ClInclude Include="FramebufferShaderGen.h"> + <Filter>Shader Generators</Filter> + </ClInclude> + <ClInclude Include="FramebufferManager.h"> + <Filter>Base</Filter> + </ClInclude> + <ClInclude Include="TextureConverterShaderGen.h"> + <Filter>Shader Generators</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <Text Include="CMakeLists.txt" /> diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index a849c7c784..3b1b22dc89 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -145,8 +145,6 @@ void VideoConfig::Refresh() bEFBAccessEnable = Config::Get(Config::GFX_HACK_EFB_ACCESS_ENABLE); bBBoxEnable = Config::Get(Config::GFX_HACK_BBOX_ENABLE); - bBBoxPreferStencilImplementation = - Config::Get(Config::GFX_HACK_BBOX_PREFER_STENCIL_IMPLEMENTATION); bForceProgressive = Config::Get(Config::GFX_HACK_FORCE_PROGRESSIVE); bSkipEFBCopyToRam = Config::Get(Config::GFX_HACK_SKIP_EFB_COPY_TO_RAM); bSkipXFBCopyToRam = Config::Get(Config::GFX_HACK_SKIP_XFB_COPY_TO_RAM); diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index eace03b4a2..9271762015 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -114,7 +114,6 @@ struct VideoConfig final bool bEFBAccessEnable; bool bPerfQueriesEnable; bool bBBoxEnable; - bool bBBoxPreferStencilImplementation; // OpenGL-only, to see how slow it is compared to SSBOs bool bForceProgressive; bool bEFBEmulateFormatChanges; @@ -186,6 +185,7 @@ struct VideoConfig final std::string AdapterName; // for OpenGL u32 MaxTextureSize; + bool bUsesLowerLeftOrigin; bool bSupportsExclusiveFullscreen; bool bSupportsDualSourceBlend; @@ -215,6 +215,7 @@ struct VideoConfig final bool bSupportsBPTCTextures; bool bSupportsFramebufferFetch; // Used as an alternative to dual-source blend on GLES bool bSupportsBackgroundCompiling; + bool bSupportsLargePoints; } backend_info; // Utility @@ -223,12 +224,6 @@ struct VideoConfig final { return backend_info.bSupportsExclusiveFullscreen && !bBorderlessFullscreen; } - bool BBoxUseFragmentShaderImplementation() const - { - if (backend_info.api_type == APIType::OpenGL && bBBoxPreferStencilImplementation) - return false; - return backend_info.bSupportsBBox && backend_info.bSupportsFragmentStoresAndAtomics; - } bool UseGPUTextureDecoding() const { return backend_info.bSupportsGPUTextureDecoding && bEnableGPUTextureDecoding; |
