diff --git a/UnleashedRecomp/gpu/video.cpp b/UnleashedRecomp/gpu/video.cpp index 3b061da..3dce587 100644 --- a/UnleashedRecomp/gpu/video.cpp +++ b/UnleashedRecomp/gpu/video.cpp @@ -285,6 +285,8 @@ static bool g_vulkan = false; static constexpr bool g_vulkan = true; #endif +static bool g_mesaTriangleStripWorkaround = false; + static constexpr bool g_hardwareResolve = true; static constexpr bool g_hardwareDepthResolve = true; @@ -1699,6 +1701,14 @@ bool Video::CreateHostDevice(const char *sdlVideoDriver) g_vulkan = (interfaceFunction == CreateVulkanInterfaceWrapper); #endif + + if (interfaceFunction == CreateVulkanInterfaceWrapper) + { + // Enable triangle strip workaround if we are on the Mesa RADV driver, as it currently has a bug where + // restart indices cause triangles to be culled incorrectly. Converting them to degenerate triangles fixes it. + g_mesaTriangleStripWorkaround = deviceDescription.name.find(" (RADV ") != std::string::npos; + } + break; } } @@ -2318,6 +2328,7 @@ static void DrawProfiler() ImGui::Text("Present Wait: %s", g_capabilities.presentWait ? "Supported" : "Unsupported"); ImGui::Text("Triangle Fan: %s", g_capabilities.triangleFan ? "Supported" : "Unsupported"); ImGui::Text("Dynamic Depth Bias: %s", g_capabilities.dynamicDepthBias ? "Supported" : "Unsupported"); + ImGui::Text("Triangle Strip Workaround: %s", g_mesaTriangleStripWorkaround ? "Enabled" : "Disabled"); ImGui::NewLine(); ImGui::Text("API: %s", g_vulkan ? "Vulkan" : "D3D12"); @@ -7402,6 +7413,187 @@ bool FxShadowMapMidAsmHook(PPCRegister& r4, PPCRegister& r5, PPCRegister& r6, PP } } +// There is a driver bug on Mesa where restart indices cause incorrect culling and prevent some triangles from being rendered. +// Restart indices can be converted to degenerate triangles as a workaround until this issue gets fixed. +static void ConvertToDegenerateTriangles(uint16_t* indices, uint32_t indexCount, uint16_t*& newIndices, uint32_t& newIndexCount) +{ + newIndices = reinterpret_cast(g_userHeap.Alloc(indexCount * sizeof(uint16_t) * 3)); + newIndexCount = 0; + + bool stripStart = true; + uint32_t stripSize = 0; + uint16_t lastIndex = 0; + + for (uint32_t i = 0; i < indexCount; i++) + { + uint16_t index = indices[i]; + if (index == 0xFFFF) + { + if ((stripSize % 2) != 0) + newIndices[newIndexCount++] = lastIndex; + + stripStart = true; + stripSize = 0; + } + else + { + if (stripStart && newIndexCount != 0) + { + newIndices[newIndexCount++] = lastIndex; + newIndices[newIndexCount++] = index; + } + + newIndices[newIndexCount++] = index; + stripStart = false; + ++stripSize; + lastIndex = index; + } + } +} + +struct MeshResource +{ + SWA_INSERT_PADDING(0x4); + be indexCount; + be indices; +}; + +static std::vector g_newIndicesToFree; + +// Hedgehog::Mirage::CMeshData::Make +PPC_FUNC_IMPL(__imp__sub_82E44AF8); +PPC_FUNC(sub_82E44AF8) +{ + uint16_t* newIndicesToFree = nullptr; + + auto databaseData = reinterpret_cast(base + ctx.r3.u32); + if (g_mesaTriangleStripWorkaround && !databaseData->IsMadeOne()) + { + auto meshResource = reinterpret_cast(base + ctx.r4.u32); + + if (meshResource->indexCount != 0) + { + uint16_t* newIndices; + uint32_t newIndexCount; + + ConvertToDegenerateTriangles( + reinterpret_cast(base + meshResource->indices), + meshResource->indexCount, + newIndices, + newIndexCount); + + meshResource->indexCount = newIndexCount; + meshResource->indices = static_cast(reinterpret_cast(newIndices) - base); + + if (PPC_LOAD_U32(0x83396E98) != NULL) + { + // If index buffers are getting merged, new indices need to survive until the merge happens. + g_newIndicesToFree.push_back(newIndices); + } + else + { + // Otherwise, we can free it immediately. + newIndicesToFree = newIndices; + } + } + } + + __imp__sub_82E44AF8(ctx, base); + + if (newIndicesToFree != nullptr) + g_userHeap.Free(newIndicesToFree); +} + +// Hedgehog::Mirage::CShareVertexBuffer::Reset +PPC_FUNC_IMPL(__imp__sub_82E250D0); +PPC_FUNC(sub_82E250D0) +{ + __imp__sub_82E250D0(ctx, base); + + for (auto newIndicesToFree : g_newIndicesToFree) + g_userHeap.Free(newIndicesToFree); + + g_newIndicesToFree.clear(); +} + +struct LightAndIndexBufferResourceV1 +{ + SWA_INSERT_PADDING(0x4); + be indexCount; + be indices; +}; + +// Hedgehog::Mirage::CLightAndIndexBufferData::MakeV1 +PPC_FUNC_IMPL(__imp__sub_82E3AFC8); +PPC_FUNC(sub_82E3AFC8) +{ + uint16_t* newIndices = nullptr; + + auto databaseData = reinterpret_cast(base + ctx.r3.u32); + if (g_mesaTriangleStripWorkaround && !databaseData->IsMadeOne()) + { + auto lightAndIndexBufferResource = reinterpret_cast(base + ctx.r4.u32); + + if (lightAndIndexBufferResource->indexCount != 0) + { + uint32_t newIndexCount; + + ConvertToDegenerateTriangles( + reinterpret_cast(base + lightAndIndexBufferResource->indices), + lightAndIndexBufferResource->indexCount, + newIndices, + newIndexCount); + + lightAndIndexBufferResource->indexCount = newIndexCount; + lightAndIndexBufferResource->indices = static_cast(reinterpret_cast(newIndices) - base); + } + } + + __imp__sub_82E3AFC8(ctx, base); + + if (newIndices != nullptr) + g_userHeap.Free(newIndices); +} + +struct LightAndIndexBufferResourceV5 +{ + SWA_INSERT_PADDING(0x8); + be indexCount; + be indices; +}; + +// Hedgehog::Mirage::CLightAndIndexBufferData::MakeV5 +PPC_FUNC_IMPL(__imp__sub_82E3B1C0); +PPC_FUNC(sub_82E3B1C0) +{ + uint16_t* newIndices = nullptr; + + auto databaseData = reinterpret_cast(base + ctx.r3.u32); + if (g_mesaTriangleStripWorkaround && !databaseData->IsMadeOne()) + { + auto lightAndIndexBufferResource = reinterpret_cast(base + ctx.r4.u32); + + if (lightAndIndexBufferResource->indexCount != 0) + { + uint32_t newIndexCount; + + ConvertToDegenerateTriangles( + reinterpret_cast(base + lightAndIndexBufferResource->indices), + lightAndIndexBufferResource->indexCount, + newIndices, + newIndexCount); + + lightAndIndexBufferResource->indexCount = newIndexCount; + lightAndIndexBufferResource->indices = static_cast(reinterpret_cast(newIndices) - base); + } + } + + __imp__sub_82E3B1C0(ctx, base); + + if (newIndices != nullptr) + g_userHeap.Free(newIndices); +} + GUEST_FUNCTION_HOOK(sub_82BD99B0, CreateDevice); GUEST_FUNCTION_HOOK(sub_82BE6230, DestructResource);