diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index 88c4ee90d5..3b5640521c 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -185,10 +185,10 @@ VkShaderStageFlagBits StageToVulkan(ShaderStage stage) { // invoke Compile again to recreate the shader then link them together. class VKShaderModule : public ShaderModule { public: - VKShaderModule(ShaderStage stage, const std::string &tag) : stage_(stage), tag_(tag) { + VKShaderModule(VulkanContext *vulkan, ShaderStage stage, const std::string &tag) : vulkan_(vulkan), stage_(stage), tag_(tag) { vkstage_ = StageToVulkan(stage); } - bool Compile(VulkanContext *vulkan, ShaderLanguage language, const uint8_t *data, size_t size); + bool Compile(ShaderLanguage language, const uint8_t *data, size_t size); const std::string &GetSource() const { return source_; } ~VKShaderModule() { if (module_) { @@ -210,8 +210,7 @@ private: std::string tag_; }; -bool VKShaderModule::Compile(VulkanContext *vulkan, ShaderLanguage language, const uint8_t *data, size_t size) { - vulkan_ = vulkan; +bool VKShaderModule::Compile(ShaderLanguage language, const uint8_t *data, size_t size) { // We'll need this to free it later. source_ = (const char *)data; std::vector spirv; @@ -230,7 +229,7 @@ bool VKShaderModule::Compile(VulkanContext *vulkan, ShaderLanguage language, con } #endif - if (vulkan->CreateShaderModule(spirv, &module_)) { + if (vulkan_->CreateShaderModule(spirv, &module_)) { ok_ = true; } else { WARN_LOG(G3D, "vkCreateShaderModule failed"); @@ -1052,6 +1051,8 @@ Pipeline *VKContext::CreateGraphicsPipeline(const PipelineDesc &desc) { ERROR_LOG(G3D, "CreateGraphicsPipeline got passed a null shader"); return nullptr; } + // Here we could addref the shader modules, and store them in VkPipeline for later Release - + // however, are NOT, or at least should not, be needed after creation, so not much point. VkPipelineShaderStageCreateInfo &stage = stages[i++]; stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; stage.pNext = nullptr; @@ -1280,8 +1281,9 @@ void VKContext::BindTextures(int start, int count, Texture **textures) { } ShaderModule *VKContext::CreateShaderModule(ShaderStage stage, ShaderLanguage language, const uint8_t *data, size_t size, const std::string &tag) { - VKShaderModule *shader = new VKShaderModule(stage, tag); - if (shader->Compile(vulkan_, language, data, size)) { + _CrtCheckMemory(); + VKShaderModule *shader = new VKShaderModule(vulkan_, stage, tag); + if (shader->Compile(language, data, size)) { return shader; } else { ERROR_LOG(G3D, "Failed to compile shader:\n%s", (const char *)data); diff --git a/Common/GPU/thin3d.cpp b/Common/GPU/thin3d.cpp index 42349b4991..8439392b43 100644 --- a/Common/GPU/thin3d.cpp +++ b/Common/GPU/thin3d.cpp @@ -68,6 +68,7 @@ bool DataFormatIsDepthStencil(DataFormat fmt) { bool RefCountedObject::Release() { + _assert_(this != nullptr); if (refcount_ > 0 && refcount_ < 10000) { if (--refcount_ == 0) { delete this; diff --git a/Common/Render/DrawBuffer.cpp b/Common/Render/DrawBuffer.cpp index 35e3f0176b..2b07155c07 100644 --- a/Common/Render/DrawBuffer.cpp +++ b/Common/Render/DrawBuffer.cpp @@ -24,13 +24,13 @@ DrawBuffer::~DrawBuffer() { delete [] verts_; } -void DrawBuffer::Init(Draw::DrawContext *t3d, Draw::Pipeline *pipeline) { +void DrawBuffer::Init(Draw::DrawContext *draw, Draw::Pipeline *pipeline) { using namespace Draw; if (inited_) return; - draw_ = t3d; + draw_ = draw; inited_ = true; if (pipeline->RequiresBuffer()) { @@ -40,7 +40,7 @@ void DrawBuffer::Init(Draw::DrawContext *t3d, Draw::Pipeline *pipeline) { } } -Draw::InputLayout *DrawBuffer::CreateInputLayout(Draw::DrawContext *t3d) { +Draw::InputLayout *DrawBuffer::CreateInputLayout(Draw::DrawContext *draw) { using namespace Draw; InputLayoutDesc desc = { { @@ -53,7 +53,7 @@ Draw::InputLayout *DrawBuffer::CreateInputLayout(Draw::DrawContext *t3d) { }, }; - return t3d->CreateInputLayout(desc); + return draw->CreateInputLayout(desc); } void DrawBuffer::Shutdown() { diff --git a/GPU/Common/FragmentShaderGenerator.cpp b/GPU/Common/FragmentShaderGenerator.cpp index cb965bf782..1468fcf572 100644 --- a/GPU/Common/FragmentShaderGenerator.cpp +++ b/GPU/Common/FragmentShaderGenerator.cpp @@ -112,7 +112,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu shading = doFlatShading ? "flat" : ""; bool earlyFragmentTests = ((!enableAlphaTest && !enableColorTest) || testForceToZero) && !gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT); - bool useAdrenoBugWorkaround = id.Bit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL); + bool useDiscardStencilBugWorkaround = id.Bit(FS_BIT_NO_DEPTH_CANNOT_DISCARD_STENCIL); bool readFramebuffer = replaceBlend == REPLACE_BLEND_COPY_FBO || colorWriteMask; bool readFramebufferTex = readFramebuffer && !gstate_c.Supports(GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH); @@ -133,7 +133,7 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu if (compat.shaderLanguage == ShaderLanguage::GLSL_VULKAN) { if (earlyFragmentTests) { WRITE(p, "layout (early_fragment_tests) in;\n"); - } else if (useAdrenoBugWorkaround && !gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT)) { + } else if (useDiscardStencilBugWorkaround && !gstate_c.Supports(GPU_ROUND_FRAGMENT_DEPTH_TO_16BIT)) { WRITE(p, "layout (depth_unchanged) out float gl_FragDepth;\n"); } @@ -1052,9 +1052,11 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu WRITE(p, " z = (1.0/65535.0) * floor(z * 65535.0);\n"); } WRITE(p, " gl_FragDepth = z;\n"); - } else if (!earlyFragmentTests && useAdrenoBugWorkaround) { - // Adreno (and possibly MESA/others) apply early frag tests even with discard in the shader. - // Writing depth prevents the bug, even with depth_unchanged specified. + } else if (!earlyFragmentTests && useDiscardStencilBugWorkaround) { + // Older Adreno drivers and many Mali drivers apply early frag tests even with discard in the shader, + // in some situations. + // For Adreno, writing depth reliably prevents the bug, even with depth_unchanged specified. + // On Mali, results so far are mixed but this workaround works in many situations. WRITE(p, " gl_FragDepth = gl_FragCoord.z;\n"); } diff --git a/UI/GPUDriverTestScreen.cpp b/UI/GPUDriverTestScreen.cpp index 10a6640520..33268e0611 100644 --- a/UI/GPUDriverTestScreen.cpp +++ b/UI/GPUDriverTestScreen.cpp @@ -231,6 +231,64 @@ static const std::vector vsFlat = { } }; +// Focused test for Mali on Vulkan. + +static const std::vector fsMaliDiscard = { + {ShaderLanguage::GLSL_VULKAN, + R"(#version 450 + #extension GL_ARB_separate_shader_objects : enable + #extension GL_ARB_shading_language_420pack : enable + layout(location = 0) in vec4 oColor0; + layout(location = 1) in vec2 oTexCoord0; + layout(location = 0) out vec4 fragColor0; + layout(set = 0, binding = 1) uniform sampler2D Sampler0; + void main() { + vec4 color = texture(Sampler0, oTexCoord0) * oColor0; + if (color.a <= 0.0) + discard; + fragColor0 = color; + })" + }, +}; + +static const std::vector fsMaliTest = { + {ShaderLanguage::GLSL_VULKAN, + R"(#version 450 + #extension GL_ARB_separate_shader_objects : enable + #extension GL_ARB_shading_language_420pack : enable + layout(location = 0) in vec4 oColor0; + layout(location = 1) in vec2 oTexCoord0; + layout(location = 0) out vec4 fragColor0; + layout(set = 0, binding = 1) uniform sampler2D Sampler0; + void main() { + fragColor0 = texture(Sampler0, oTexCoord0) * oColor0;; + })" + }, +}; + +static const std::vector vsMaliTest = { + { ShaderLanguage::GLSL_VULKAN, + R"(#version 450 + #extension GL_ARB_separate_shader_objects : enable + #extension GL_ARB_shading_language_420pack : enable + layout (std140, set = 0, binding = 0) uniform bufferVals { + mat4 WorldViewProj; + } myBufferVals; + layout (location = 0) in vec4 pos; + layout (location = 1) in vec4 inColor; + layout (location = 2) in vec2 inTexCoord; + layout (location = 0) out vec4 outColor; + layout (location = 1) out highp vec2 outTexCoord; + out gl_PerVertex { vec4 gl_Position; }; + void main() { + outColor = inColor; + outTexCoord = inTexCoord; + gl_Position = myBufferVals.WorldViewProj * pos; + })" + } +}; + + GPUDriverTestScreen::GPUDriverTestScreen() { using namespace Draw; } @@ -280,6 +338,20 @@ GPUDriverTestScreen::~GPUDriverTestScreen() { if (samplerNearest_) samplerNearest_->Release(); + + if (chessTexture_) + chessTexture_->Release(); + + if (circleTexture_) + circleTexture_->Release(); + + for (auto pipeline : maliWriteStencilPipelines_) { + pipeline->Release(); + } + + for (auto pipeline : maliReadStencilPipelines_) { + pipeline->Release(); + } } void GPUDriverTestScreen::CreateViews() { @@ -301,20 +373,184 @@ void GPUDriverTestScreen::CreateViews() { tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 30.0f, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT, false)); anchor->Add(tabHolder_); + tabHolder_->AddTab("Discard", new LinearLayout(ORIENT_VERTICAL)); tabHolder_->AddTab("Shader", new LinearLayout(ORIENT_VERTICAL)); + // TODO: Check for Vulkan + tabHolder_->AddTab("Mali Discard", new LinearLayout(ORIENT_VERTICAL)); + + tabHolder_->SetCurrentTab(2, true); + Choice *back = new Choice(di->T("Back"), "", false, new AnchorLayoutParams(100, WRAP_CONTENT, 10, NONE, NONE, 10)); - back->OnClick.Handle(this, &UIScreen::OnBack); + back->OnClick.Handle (this, &UIScreen::OnBack); anchor->Add(back); } +// In this path we're trying to get as close as possible to the situation in Surf's Up, which breaks. +void GPUDriverTestScreen::MaliDiscardTest() { + using namespace UI; + using namespace Draw; + + UIContext &dc = *screenManager()->getUIContext(); + DrawContext *draw = dc.GetDrawContext(); + + if (!chessTexture_) { + uint8_t *data = new uint8_t[32 * 32]; + for (int y = 0; y < 32; y++) { + for (int x = 0; x < 32; x++) { + data[y * 32 + x] = ((x ^ y) & 1) * 255; + } + } + + TextureDesc desc{}; + desc.width = 32; + desc.height = 32; + desc.depth = 1; + desc.mipLevels = 1; + desc.format = DataFormat::R8_UNORM; + desc.generateMips = false; + desc.initData.push_back(data); + chessTexture_ = draw->CreateTexture(desc); + delete[] data; + } + + if (!circleTexture_) { + uint8_t *data = new uint8_t[32 * 32]; + for (int y = 0; y < 32; y++) { + float dy = (y - 16.0f) / 16.0f; + for (int x = 0; x < 32; x++) { + float dx = (x - 16.0f) / 16.0f; + float alpha = std::max(0.0, 1.0 - sqrtf(dx * dx + dy * dy)); + data[y * 32 + x] = (uint8_t)(alpha * 255.0); + } + } + + TextureDesc desc{}; + desc.width = 32; + desc.height = 32; + desc.depth = 1; + desc.mipLevels = 1; + desc.format = DataFormat::R8_UNORM; + desc.generateMips = false; + desc.initData.push_back(data); + circleTexture_ = draw->CreateTexture(desc); + delete[] data; + } + _CrtCheckMemory(); + if (maliWriteStencilPipelines_.empty()) { + InputLayout *inputLayout = ui_draw2d.CreateInputLayout(draw); + + ShaderModule *maliVs = CreateShader(draw, ShaderStage::Vertex, vsMaliTest); + ShaderModule *maliFsWrite = CreateShader(draw, ShaderStage::Fragment, fsMaliDiscard); + ShaderModule *maliFsRead = CreateShader(draw, ShaderStage::Fragment, fsMaliTest); + + BlendStateDesc desc{}; + desc.colorMask = 0xF; + desc.srcCol = BlendFactor::SRC_ALPHA; + desc.dstCol = BlendFactor::ONE_MINUS_SRC_ALPHA; + desc.srcAlpha = BlendFactor::ONE; + desc.dstAlpha = BlendFactor::ZERO; + desc.enabled = true; + BlendState *blendOn = draw->CreateBlendState(desc); + + DepthStencilStateDesc writeStencil{}; + writeStencil.stencilEnabled = true; + writeStencil.front.passOp = StencilOp::INCREMENT_AND_CLAMP; + writeStencil.front.failOp = StencilOp::KEEP; + writeStencil.front.depthFailOp = StencilOp::KEEP; + writeStencil.front.compareMask = 0xFF; + writeStencil.front.compareOp = Comparison::ALWAYS; + writeStencil.front.writeMask = 0x0; + writeStencil.back = writeStencil.front; + + DepthStencilState *writeStencilState = draw->CreateDepthStencilState(writeStencil); + + DepthStencilStateDesc readStencil{}; + readStencil.stencilEnabled = true; + readStencil.front.passOp = StencilOp::KEEP; + readStencil.front.failOp = StencilOp::KEEP; + readStencil.front.depthFailOp = StencilOp::KEEP; + readStencil.front.compareMask = 0xFF; + readStencil.front.compareOp = Comparison::NOT_EQUAL; + readStencil.front.writeMask = 0x0; + readStencil.back = readStencil.front; + + DepthStencilState *readStencilState = draw->CreateDepthStencilState(readStencil); + + std::vector shaders; + shaders.push_back(maliVs); + shaders.push_back(maliFsWrite); + RasterState *rasterNoCull = draw->CreateRasterState({}); + + Pipeline *maliWritePipeline = draw->CreateGraphicsPipeline(PipelineDesc{ + Primitive::TRIANGLE_LIST, + shaders, + inputLayout, + writeStencilState, + blendOn, + rasterNoCull, + }); + + shaders.clear(); + shaders.push_back(maliVs); + shaders.push_back(maliFsRead); + + Pipeline *maliReadPipeline = draw->CreateGraphicsPipeline(PipelineDesc{ + Primitive::TRIANGLE_LIST, + shaders, + inputLayout, + readStencilState, + blendOn, + rasterNoCull, + }); + + inputLayout->Release(); + blendOn->Release(); + rasterNoCull->Release(); + + writeStencilState->Release(); + readStencilState->Release(); + + maliFsRead->Release(); + maliFsWrite->Release(); + maliVs->Release(); + + maliReadStencilPipelines_.push_back(maliReadPipeline); + maliWriteStencilPipelines_.push_back(maliWritePipeline); + } + + _CrtCheckMemory(); + + for (int i = 0; i < maliReadStencilPipelines_.size(); i++) { + Bounds bounds = { 40.0f, 40.0f + (float)i * 100.0f, 96.0f, 96.0f }; + dc.BeginPipeline(maliReadStencilPipelines_[i], samplerNearest_); + draw->SetStencilRef(0x0); + draw->BindTexture(0, circleTexture_); + dc.SetCurZ(0.1f); + dc.FillRect(UI::Drawable(0xFFFFFFFF), bounds); + dc.Flush(); + + /* + dc.BeginPipeline(maliWriteStencilPipelines_[i], samplerNearest_); + draw->SetStencilRef(0x0); + draw->BindTexture(0, chessTexture_); + dc.SetCurZ(0.1f); + dc.FillRect(UI::Drawable(0xFFFFFFFF), bounds); + dc.Flush(); + */ + } + _CrtCheckMemory(); +} + void GPUDriverTestScreen::DiscardTest() { using namespace UI; using namespace Draw; - if (!discardWriteDepthStencil_) { - DrawContext *draw = screenManager()->getDrawContext(); + UIContext &dc = *screenManager()->getUIContext(); + DrawContext *draw = dc.GetDrawContext(); + + if (!discardWriteDepthStencil_) { // Create the special shader module. discardFragShader_ = CreateShader(draw, ShaderStage::Fragment, fsDiscard); @@ -447,9 +683,6 @@ void GPUDriverTestScreen::DiscardTest() { rasterNoCull->Release(); } - UIContext &dc = *screenManager()->getUIContext(); - Draw::DrawContext *draw = dc.GetDrawContext(); - static const char * const writeModeNames[] = { "Stencil+Depth", "Stencil", "Depth" }; Pipeline *writePipelines[] = { discardWriteDepthStencil_, discardWriteStencil_, discardWriteDepth_ }; const int numWriteModes = ARRAY_SIZE(writeModeNames); @@ -647,5 +880,8 @@ void GPUDriverTestScreen::render() { case 1: ShaderTest(); break; + case 2: + MaliDiscardTest(); + break; } } diff --git a/UI/GPUDriverTestScreen.h b/UI/GPUDriverTestScreen.h index f2c5796be1..54b3848dc6 100644 --- a/UI/GPUDriverTestScreen.h +++ b/UI/GPUDriverTestScreen.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "Common/System/Display.h" #include "Common/UI/Context.h" #include "Common/UI/View.h" @@ -18,6 +20,7 @@ public: void render() override; private: + void MaliDiscardTest(); void DiscardTest(); void ShaderTest(); @@ -44,6 +47,12 @@ private: Draw::Pipeline *drawTestDepthLessEqual_ = nullptr; Draw::Pipeline *drawTestDepthGreater_ = nullptr; + // Mali discard pipelines + Draw::Texture *circleTexture_ = nullptr; + Draw::Texture *chessTexture_ = nullptr; + std::vector maliWriteStencilPipelines_; + std::vector maliReadStencilPipelines_; + // Shader tests // ============ diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index cc1e08ff4d..57d820afd8 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -836,7 +836,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch } // Easy testing - // screenManager->push(new GPUDriverTestScreen()); + screenManager->push(new GPUDriverTestScreen()); if (g_Config.bRemoteShareOnStartup && g_Config.bRemoteDebuggerOnStartup) StartWebServer(WebServerFlags::ALL);