diff --git a/Common/GPU/D3D11/thin3d_d3d11.cpp b/Common/GPU/D3D11/thin3d_d3d11.cpp index 2bce9ebaff..c17bb39919 100644 --- a/Common/GPU/D3D11/thin3d_d3d11.cpp +++ b/Common/GPU/D3D11/thin3d_d3d11.cpp @@ -105,6 +105,7 @@ public: void InvalidateCachedState() override; void BindTextures(int start, int count, Texture **textures) override; + void BindNativeTexture(int index, void *nativeTexture) override; void BindSamplerStates(int start, int count, SamplerState **states) override; void BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) override; void BindIndexBuffer(Buffer *indexBuffer, int offset) override; @@ -471,6 +472,7 @@ static DXGI_FORMAT dataFormatToD3D11(DataFormat format) { case DataFormat::R8G8B8A8_UNORM_SRGB: return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; case DataFormat::B8G8R8A8_UNORM: return DXGI_FORMAT_B8G8R8A8_UNORM; case DataFormat::B8G8R8A8_UNORM_SRGB: return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; + case DataFormat::R16_UNORM: return DXGI_FORMAT_R16_UNORM; case DataFormat::R16_FLOAT: return DXGI_FORMAT_R16_FLOAT; case DataFormat::R16G16_FLOAT: return DXGI_FORMAT_R16G16_FLOAT; case DataFormat::R16G16B16A16_FLOAT: return DXGI_FORMAT_R16G16B16A16_FLOAT; @@ -1388,6 +1390,12 @@ void D3D11DrawContext::BindTextures(int start, int count, Texture **textures) { context_->PSSetShaderResources(start, count, views); } +void D3D11DrawContext::BindNativeTexture(int index, void *nativeTexture) { + // Collect the resource views from the textures. + ID3D11ShaderResourceView *view = (ID3D11ShaderResourceView *)nativeTexture; + context_->PSSetShaderResources(index, 1, &view); +} + void D3D11DrawContext::BindSamplerStates(int start, int count, SamplerState **states) { ID3D11SamplerState *samplers[MAX_BOUND_TEXTURES]; _assert_(start + count <= ARRAY_SIZE(samplers)); diff --git a/Common/GPU/D3D9/thin3d_d3d9.cpp b/Common/GPU/D3D9/thin3d_d3d9.cpp index 8554961119..a84d42f64e 100644 --- a/Common/GPU/D3D9/thin3d_d3d9.cpp +++ b/Common/GPU/D3D9/thin3d_d3d9.cpp @@ -114,6 +114,7 @@ static const D3DSTENCILOP stencilOpToD3D9[] = { D3DFORMAT FormatToD3DFMT(DataFormat fmt) { switch (fmt) { + case DataFormat::R16_UNORM: return D3DFMT_L16; // closest match, should be a fine substitution if we ignore channels except R. case DataFormat::R8G8B8A8_UNORM: return D3DFMT_A8R8G8B8; case DataFormat::B8G8R8A8_UNORM: return D3DFMT_A8R8G8B8; case DataFormat::R4G4B4A4_UNORM_PACK16: return D3DFMT_A4R4G4B4; // emulated @@ -442,6 +443,17 @@ void D3D9Texture::SetImageData(int x, int y, int z, int width, int height, int d if (data != rect.pBits) memcpy(dest, source, sizeof(uint32_t) * width); break; + + case DataFormat::R8_UNORM: + if (data != rect.pBits) + memcpy(dest, source, width); + break; + + case DataFormat::R16_UNORM: + if (data != rect.pBits) + memcpy(dest, source, sizeof(uint16_t) * width); + break; + default: // Unhandled data format copy. DebugBreak(); @@ -520,6 +532,8 @@ public: void GetFramebufferDimensions(Framebuffer *fbo, int *w, int *h) override; void BindTextures(int start, int count, Texture **textures) override; + void BindNativeTexture(int index, void *nativeTexture) override; + void BindSamplerStates(int start, int count, SamplerState **states) override { _assert_(start + count <= MAX_BOUND_TEXTURES); for (int i = 0; i < count; ++i) { @@ -813,6 +827,11 @@ void D3D9Context::BindTextures(int start, int count, Texture **textures) { } } +void D3D9Context::BindNativeTexture(int index, void *nativeTexture) { + LPDIRECT3DTEXTURE9 texture = (LPDIRECT3DTEXTURE9)nativeTexture; + device_->SetTexture(index, texture); +} + void D3D9Context::EndFrame() { curPipeline_ = nullptr; } diff --git a/Common/GPU/DataFormat.h b/Common/GPU/DataFormat.h index b07d0a2f34..0c8a93e627 100644 --- a/Common/GPU/DataFormat.h +++ b/Common/GPU/DataFormat.h @@ -32,6 +32,8 @@ enum class DataFormat : uint8_t { A1R5G5B5_UNORM_PACK16, // A1 in the UPPER bit. A1B5G5R5_UNORM_PACK16, // A1 in the UPPER bit. OpenGL-only. + R16_UNORM, + R16_FLOAT, R16G16_FLOAT, R16G16B16A16_FLOAT, diff --git a/Common/GPU/OpenGL/DataFormatGL.cpp b/Common/GPU/OpenGL/DataFormatGL.cpp index 1b8ba63acc..48e439a76d 100644 --- a/Common/GPU/OpenGL/DataFormatGL.cpp +++ b/Common/GPU/OpenGL/DataFormatGL.cpp @@ -4,9 +4,23 @@ namespace Draw { // TODO: Also output storage format (GL_RGBA8 etc) for modern GL usage. -bool Thin3DFormatToFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment) { +bool Thin3DFormatToGLFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment) { alignment = 4; switch (fmt) { + case DataFormat::R16_UNORM: + internalFormat = GL_RGBA; + format = GL_RED; + type = GL_UNSIGNED_SHORT; + alignment = 2; + break; + + case DataFormat::R8_UNORM: + internalFormat = GL_RGBA; + format = GL_RED; + type = GL_UNSIGNED_BYTE; + alignment = 1; + break; + case DataFormat::R8G8B8A8_UNORM: internalFormat = GL_RGBA; format = GL_RGBA; diff --git a/Common/GPU/OpenGL/DataFormatGL.h b/Common/GPU/OpenGL/DataFormatGL.h index 63fc0a22ec..163f155db3 100644 --- a/Common/GPU/OpenGL/DataFormatGL.h +++ b/Common/GPU/OpenGL/DataFormatGL.h @@ -5,6 +5,6 @@ namespace Draw { -bool Thin3DFormatToFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment); +bool Thin3DFormatToGLFormatAndType(DataFormat fmt, GLuint &internalFormat, GLuint &format, GLuint &type, int &alignment); } diff --git a/Common/GPU/OpenGL/GLQueueRunner.cpp b/Common/GPU/OpenGL/GLQueueRunner.cpp index 53bd73342a..ee850b2ad3 100644 --- a/Common/GPU/OpenGL/GLQueueRunner.cpp +++ b/Common/GPU/OpenGL/GLQueueRunner.cpp @@ -385,7 +385,7 @@ void GLQueueRunner::RunInitSteps(const std::vector &steps, bool ski GLenum internalFormat, format, type; int alignment; - Thin3DFormatToFormatAndType(step.texture_image.format, internalFormat, format, type, alignment); + Thin3DFormatToGLFormatAndType(step.texture_image.format, internalFormat, format, type, alignment); if (step.texture_image.depth == 1) { glTexImage2D(tex->target, step.texture_image.level, internalFormat, @@ -1283,7 +1283,7 @@ void GLQueueRunner::PerformRenderPass(const GLRStep &step, bool first, bool last // For things to show in RenderDoc, need to split into glTexImage2D(..., nullptr) and glTexSubImage. GLuint internalFormat, format, type; int alignment; - Thin3DFormatToFormatAndType(c.texture_subimage.format, internalFormat, format, type, alignment); + Thin3DFormatToGLFormatAndType(c.texture_subimage.format, internalFormat, format, type, alignment); glTexSubImage2D(tex->target, c.texture_subimage.level, c.texture_subimage.x, c.texture_subimage.y, c.texture_subimage.width, c.texture_subimage.height, format, type, c.texture_subimage.data); if (c.texture_subimage.allocType == GLRAllocType::ALIGNED) { FreeAlignedMemory(c.texture_subimage.data); diff --git a/Common/GPU/OpenGL/GLRenderManager.cpp b/Common/GPU/OpenGL/GLRenderManager.cpp index efdf0bd7f4..ae5cb9dd17 100644 --- a/Common/GPU/OpenGL/GLRenderManager.cpp +++ b/Common/GPU/OpenGL/GLRenderManager.cpp @@ -235,11 +235,29 @@ bool GLRenderManager::ThreadFrame() { INFO_LOG(G3D, "Running first frame (%d)", threadFrame_); firstFrame = false; } + + // Start of an OpenXR frame. This updates user's head pose and VR timestamps. + // For fluent rendering, delay between StartVRRender and FinishVRRender must be very short. + if (IsVRBuild() && !vrRenderStarted) { + if (StartVRRender()) { + vrRenderStarted = true; + } else { + return false; + } + } + + // Render the scene. Run(threadFrame_); VLOG("PULL: Finished frame %d", threadFrame_); } while (!nextFrame); + // Post OpenXR frame on a screen. + if (IsVRBuild() && vrRenderStarted) { + FinishVRRender(); + vrRenderStarted = false; + } + return true; } @@ -579,17 +597,14 @@ void GLRenderManager::Run(int frame) { } if (IsVRBuild()) { - if (PreVRRender()) { - int passes = 1; - if (!IsMultiviewSupported() && g_Config.bEnableStereo) { - passes = 2; - } - for (int i = 0; i < passes; i++) { - PreVRFrameRender(i); - queueRunner_.RunSteps(stepsOnThread, skipGLCalls_, i < passes - 1); - PostVRFrameRender(); - } - PostVRRender(); + int passes = 1; + if (!IsMultiviewSupported() && g_Config.bEnableStereo) { + passes = 2; + } + for (int i = 0; i < passes; i++) { + PreVRFrameRender(i); + queueRunner_.RunSteps(stepsOnThread, skipGLCalls_, i < passes - 1); + PostVRFrameRender(); } } else { queueRunner_.RunSteps(stepsOnThread, skipGLCalls_); diff --git a/Common/GPU/OpenGL/GLRenderManager.h b/Common/GPU/OpenGL/GLRenderManager.h index 5423262c98..719603863e 100644 --- a/Common/GPU/OpenGL/GLRenderManager.h +++ b/Common/GPU/OpenGL/GLRenderManager.h @@ -1041,6 +1041,7 @@ private: bool nextFrame = false; bool firstFrame = true; + bool vrRenderStarted = false; GLDeleter deleter_; bool skipGLCalls_ = false; diff --git a/Common/GPU/OpenGL/thin3d_gl.cpp b/Common/GPU/OpenGL/thin3d_gl.cpp index 2e93fc8791..047cc8b8fb 100644 --- a/Common/GPU/OpenGL/thin3d_gl.cpp +++ b/Common/GPU/OpenGL/thin3d_gl.cpp @@ -401,6 +401,8 @@ public: } void BindTextures(int start, int count, Texture **textures) override; + void BindNativeTexture(int sampler, void *nativeTexture) override; + void BindPipeline(Pipeline *pipeline) override; void BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) override { _assert_(start + count <= ARRAY_SIZE(curVBuffers_)); @@ -1138,6 +1140,12 @@ void OpenGLContext::BindTextures(int start, int count, Texture **textures) { } } +void OpenGLContext::BindNativeTexture(int index, void *nativeTexture) { + GLRTexture *tex = (GLRTexture *)nativeTexture; + boundTextures_[index] = tex; + renderManager_.BindTexture(index, tex); +} + void OpenGLContext::ApplySamplers() { for (int i = 0; i < MAX_TEXTURE_SLOTS; i++) { const OpenGLSamplerState *samp = boundSamplers_[i]; @@ -1483,7 +1491,8 @@ uint32_t OpenGLContext::GetDataFormatSupport(DataFormat fmt) const { return FMT_INPUTLAYOUT; case DataFormat::R8_UNORM: - return 0; + return FMT_TEXTURE; + case DataFormat::BC1_RGBA_UNORM_BLOCK: case DataFormat::BC2_UNORM_BLOCK: case DataFormat::BC3_UNORM_BLOCK: diff --git a/Common/GPU/Vulkan/VulkanRenderManager.cpp b/Common/GPU/Vulkan/VulkanRenderManager.cpp index 10cc890009..56f40655f7 100644 --- a/Common/GPU/Vulkan/VulkanRenderManager.cpp +++ b/Common/GPU/Vulkan/VulkanRenderManager.cpp @@ -150,6 +150,8 @@ VKRFramebuffer::VKRFramebuffer(VulkanContext *vk, VkCommandBuffer initCmd, VKRRe width = _width; height = _height; + _dbg_assert_(tag); + CreateImage(vulkan_, initCmd, color, width, height, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, true, tag); CreateImage(vulkan_, initCmd, depth, width, height, vulkan_->GetDeviceInfo().preferredDepthStencilFormat, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, false, tag); diff --git a/Common/GPU/Vulkan/thin3d_vulkan.cpp b/Common/GPU/Vulkan/thin3d_vulkan.cpp index f673691224..8420c01450 100644 --- a/Common/GPU/Vulkan/thin3d_vulkan.cpp +++ b/Common/GPU/Vulkan/thin3d_vulkan.cpp @@ -414,6 +414,7 @@ public: void BindSamplerStates(int start, int count, SamplerState **state) override; void BindTextures(int start, int count, Texture **textures) override; + void BindNativeTexture(int sampler, void *nativeTexture) override; void BindPipeline(Pipeline *pipeline) override { curPipeline_ = (VKPipeline *)pipeline; @@ -563,6 +564,11 @@ static int GetBpp(VkFormat format) { case VK_FORMAT_R8G8B8A8_UNORM: case VK_FORMAT_B8G8R8A8_UNORM: return 32; + case VK_FORMAT_R8_UNORM: + return 8; + case VK_FORMAT_R8G8_UNORM: + case VK_FORMAT_R16_UNORM: + return 16; case VK_FORMAT_R4G4B4A4_UNORM_PACK16: case VK_FORMAT_B4G4R4A4_UNORM_PACK16: case VK_FORMAT_R5G5B5A1_UNORM_PACK16: @@ -586,6 +592,9 @@ static VkFormat DataFormatToVulkan(DataFormat format) { case DataFormat::D32F: return VK_FORMAT_D32_SFLOAT; case DataFormat::D32F_S8: return VK_FORMAT_D32_SFLOAT_S8_UINT; case DataFormat::S8: return VK_FORMAT_S8_UINT; + + case DataFormat::R16_UNORM: return VK_FORMAT_R16_UNORM; + case DataFormat::R16_FLOAT: return VK_FORMAT_R16_SFLOAT; case DataFormat::R16G16_FLOAT: return VK_FORMAT_R16G16_SFLOAT; case DataFormat::R16G16B16A16_FLOAT: return VK_FORMAT_R16G16B16A16_SFLOAT; @@ -1284,6 +1293,11 @@ void VKContext::BindTextures(int start, int count, Texture **textures) { } } +void VKContext::BindNativeTexture(int sampler, void *nativeTexture) { + boundTextures_[sampler] = nullptr; + boundImageView_[sampler] = (VkImageView)nativeTexture; +} + ShaderModule *VKContext::CreateShaderModule(ShaderStage stage, ShaderLanguage language, const uint8_t *data, size_t size, const char *tag) { VKShaderModule *shader = new VKShaderModule(stage, tag); if (shader->Compile(vulkan_, language, data, size)) { diff --git a/Common/GPU/thin3d.cpp b/Common/GPU/thin3d.cpp index d712195c7b..1cc3c11fa0 100644 --- a/Common/GPU/thin3d.cpp +++ b/Common/GPU/thin3d.cpp @@ -33,6 +33,9 @@ size_t DataFormatSizeInBytes(DataFormat fmt) { case DataFormat::R8G8B8A8_SNORM: return 4; case DataFormat::R8G8B8A8_UINT: return 4; case DataFormat::R8G8B8A8_SINT: return 4; + + case DataFormat::R16_UNORM: return 2; + case DataFormat::R16_FLOAT: return 2; case DataFormat::R16G16_FLOAT: return 4; case DataFormat::R16G16B16A16_FLOAT: return 8; diff --git a/Common/GPU/thin3d.h b/Common/GPU/thin3d.h index 6196f494f9..843e6e8813 100644 --- a/Common/GPU/thin3d.h +++ b/Common/GPU/thin3d.h @@ -557,6 +557,7 @@ typedef std::functionfb_format = params.fb_format; vfb->usageFlags = FB_USAGE_RENDER_COLOR; - u32 byteSize = ColorBufferByteSize(vfb); - if (Memory::IsVRAMAddress(params.fb_address) && params.fb_address + byteSize > framebufRangeEnd_) { - framebufRangeEnd_ = params.fb_address + byteSize; + u32 colorByteSize = ColorBufferByteSize(vfb); + if (Memory::IsVRAMAddress(params.fb_address) && params.fb_address + colorByteSize > framebufRangeEnd_) { + framebufRangeEnd_ = params.fb_address + colorByteSize; } // This is where we actually create the framebuffer. The true is "force". @@ -499,9 +499,9 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(Framebuffer // Assume that if we're clearing right when switching to a new framebuffer, we don't need to upload. if (useBufferedRendering_ && params.isDrawing) { - gpu->PerformMemoryUpload(params.fb_address, byteSize); + gpu->PerformMemoryUpload(params.fb_address, colorByteSize); // Alpha was already done by PerformMemoryUpload. - PerformStencilUpload(params.fb_address, byteSize, StencilUpload::STENCIL_IS_ZERO | StencilUpload::IGNORE_ALPHA); + PerformStencilUpload(params.fb_address, colorByteSize, StencilUpload::STENCIL_IS_ZERO | StencilUpload::IGNORE_ALPHA); // TODO: Is it worth trying to upload the depth buffer (only if it wasn't copied above..?) } @@ -551,9 +551,23 @@ void FramebufferManagerCommon::SetDepthFrameBuffer(bool isClearingDepth) { // by copying from any overlapping buffers with fresher content. if (!isClearingDepth) { CopyToDepthFromOverlappingFramebuffers(currentRenderVfb_); + + // Special compatibility trick for Burnout Dominator lens flares. Not sure how to best generalize this. See issue #11100 + if (PSP_CoreParameter().compat.flags().UploadDepthForCLUTTextures && (currentRenderVfb_->usageFlags & FB_USAGE_CLUT) != 0) { + // Set the flag, then upload memory contents to depth channel. + // Sanity check the depth buffer pointer. + if (currentRenderVfb_->z_address != 0 && currentRenderVfb_->z_address != currentRenderVfb_->fb_address) { + if (Memory::IsValidRange(currentRenderVfb_->z_address, currentRenderVfb_->width * 2)) { + const u16 *src = (const u16 *)Memory::GetPointerUnchecked(currentRenderVfb_->z_address); + DrawPixels(currentRenderVfb_, 0, 0, (const u8 *)src, GE_FORMAT_DEPTH16, currentRenderVfb_->z_stride, currentRenderVfb_->width, currentRenderVfb_->height, RASTER_DEPTH, "Depth Upload"); + } + } + } } + // First time use of this framebuffer's depth buffer. currentRenderVfb_->usageFlags |= FB_USAGE_RENDER_DEPTH; + currentRenderVfb_->depthBindSeq = GetBindSeqCount(); } @@ -784,8 +798,8 @@ void FramebufferManagerCommon::CopyToColorFromOverlappingFramebuffers(VirtualFra float scaleFactorX = 1.0f; pipeline = GetReinterpretPipeline(src->fb_format, dst->fb_format, &scaleFactorX); - dstX1 *= 0.5f; - dstX2 *= 0.5f; + dstX1 *= scaleFactorX; + dstX2 *= scaleFactorX; pass_name = reinterpretStrings[(int)src->fb_format][(int)dst->fb_format]; @@ -811,6 +825,11 @@ void FramebufferManagerCommon::CopyToColorFromOverlappingFramebuffers(VirtualFra } Draw2DPipeline *FramebufferManagerCommon::GetReinterpretPipeline(GEBufferFormat from, GEBufferFormat to, float *scaleFactorX) { + if (from == to) { + *scaleFactorX = 1.0f; + return Get2DPipeline(DRAW2D_COPY_COLOR); + } + if (IsBufferFormat16Bit(from) && !IsBufferFormat16Bit(to)) { // We halve the X coordinates in the destination framebuffer. // The shader will collect two pixels worth of input data and merge into one. @@ -819,6 +838,8 @@ Draw2DPipeline *FramebufferManagerCommon::GetReinterpretPipeline(GEBufferFormat // We double the X coordinates in the destination framebuffer. // The shader will sample and depending on the X coordinate & 1, use the upper or lower bits. *scaleFactorX = 2.0f; + } else { + *scaleFactorX = 1.0f; } Draw2DPipeline *pipeline = reinterpretFromTo_[(int)from][(int)to]; @@ -1017,7 +1038,7 @@ void FramebufferManagerCommon::UpdateFromMemory(u32 addr, int size) { // TODO: This doesn't seem quite right anymore. fmt = displayFormat_; } - DrawPixels(vfb, 0, 0, Memory::GetPointer(addr), fmt, vfb->fb_stride, vfb->width, vfb->height); + DrawPixels(vfb, 0, 0, Memory::GetPointer(addr), fmt, vfb->fb_stride, vfb->width, vfb->height, RASTER_COLOR, "UpdateFromMemory_DrawPixels"); SetColorUpdated(vfb, gstate_c.skipDrawReason); } else { INFO_LOG(FRAMEBUF, "Invalidating FBO for %08x (%dx%d %s)", vfb->fb_address, vfb->width, vfb->height, GeBufferFormatToString(vfb->fb_format)); @@ -1033,20 +1054,20 @@ void FramebufferManagerCommon::UpdateFromMemory(u32 addr, int size) { gstate_c.Dirty(DIRTY_FRAGMENTSHADER_STATE); } -void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int dstY, const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height) { +void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int dstY, const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height, RasterChannel channel, const char *tag) { textureCache_->ForgetLastTexture(); - shaderManager_->DirtyLastShader(); // On GL, important that this is BEFORE drawing + shaderManager_->DirtyLastShader(); float u0 = 0.0f, u1 = 1.0f; float v0 = 0.0f, v1 = 1.0f; DrawTextureFlags flags; if (useBufferedRendering_ && vfb && vfb->fbo) { - flags = DRAWTEX_LINEAR; - draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, "DrawPixels"); - gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE); + flags = channel == RASTER_COLOR ? DRAWTEX_LINEAR : DRAWTEX_NEAREST; + draw_->BindFramebufferAsRenderTarget(vfb->fbo, { Draw::RPAction::KEEP, Draw::RPAction::KEEP, Draw::RPAction::KEEP }, tag); SetViewport2D(0, 0, vfb->renderWidth, vfb->renderHeight); draw_->SetScissorRect(0, 0, vfb->renderWidth, vfb->renderHeight); } else { + _dbg_assert_(channel == RASTER_COLOR); // We are drawing directly to the back buffer so need to flip. // Should more of this be handled by the presentation engine? if (needBackBufferYSwap_) @@ -1060,10 +1081,18 @@ void FramebufferManagerCommon::DrawPixels(VirtualFramebuffer *vfb, int dstX, int draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_); } + if (channel == RASTER_DEPTH) { + _dbg_assert_(srcPixelFormat == GE_FORMAT_DEPTH16); + flags = flags | DRAWTEX_DEPTH; + } + Draw::Texture *pixelsTex = MakePixelTexture(srcPixels, srcPixelFormat, srcStride, width, height); if (pixelsTex) { draw_->BindTextures(0, 1, &pixelsTex); + + // TODO: Replace with draw2D_.Blit() directly. DrawActiveTexture(dstX, dstY, width, height, vfb->bufferWidth, vfb->bufferHeight, u0, v0, u1, v1, ROTATION_LOCKED_HORIZONTAL, flags); + gpuStats.numUploads++; pixelsTex->Release(); draw_->InvalidateCachedState(); @@ -1081,7 +1110,7 @@ bool FramebufferManagerCommon::BindFramebufferAsColorTexture(int stage, VirtualF // currentRenderVfb_ will always be set when this is called, except from the GE debugger. // Let's just not bother with the copy in that case. - bool skipCopy = !(flags & BINDFBCOLOR_MAY_COPY) || GPUStepping::IsStepping(); + bool skipCopy = !(flags & BINDFBCOLOR_MAY_COPY); // Currently rendering to this framebuffer. Need to make a copy. if (!skipCopy && framebuffer == currentRenderVfb_) { @@ -1153,6 +1182,7 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G const u16_le *src16 = (const u16_le *)srcPixels + srcStride * y; const u32_le *src32 = (const u32_le *)srcPixels + srcStride * y; u32 *dst = (u32 *)(data + byteStride * y); + u16 *dst16 = (u16 *)(data + byteStride * y); switch (srcPixelFormat) { case GE_FORMAT_565: if (preferredPixelsFormat_ == Draw::DataFormat::B8G8R8A8_UNORM) @@ -1185,18 +1215,28 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G memcpy(dst, src32, width * 4); break; - case GE_FORMAT_INVALID: case GE_FORMAT_DEPTH16: - _dbg_assert_msg_(false, "Invalid pixelFormat passed to DrawPixels()."); + // TODO: Must take the depth range into account, unless it's already 0-1. + // TODO: Depending on the color buffer format used with this depth buffer, we need + // to do one of two different swizzle operations. However, for the only use of this so far, + // the Burnout lens flare trickery, swizzle doesn't matter since it's just a 0, 7fff, 0, 7fff pattern + // which comes out the same. + memcpy(dst16, src16, w * 2); + break; + + case GE_FORMAT_INVALID: + // Bad break; } } return true; }; + // Note: For depth, we create an R16_UNORM texture, that'll be just fine for uploading depth through a shader, + // and likely more efficient. Draw::TextureDesc desc{ Draw::TextureType::LINEAR2D, - preferredPixelsFormat_, + srcPixelFormat == GE_FORMAT_DEPTH16 ? Draw::DataFormat::R16_UNORM : preferredPixelsFormat_, width, height, 1, @@ -1206,11 +1246,12 @@ Draw::Texture *FramebufferManagerCommon::MakePixelTexture(const u8 *srcPixels, G { (uint8_t *)srcPixels }, generateTexture, }; + // Hot Shots Golf (#12355) does tons of these in a frame in some situations! So creating textures // better be fast. Draw::Texture *tex = draw_->CreateTexture(desc); if (!tex) - ERROR_LOG(G3D, "Failed to create drawpixels texture"); + ERROR_LOG(G3D, "Failed to create DrawPixels texture"); return tex; } @@ -1229,7 +1270,7 @@ void FramebufferManagerCommon::DrawFramebufferToOutput(const u8 *srcPixels, int if (needBackBufferYSwap_) { flags |= OutputFlags::BACKBUFFER_FLIPPED; } - // DrawActiveTexture reverses these, probably to match "up". + // CopyToOutput reverses these, probably to match "up". if (GetGPUBackend() == GPUBackend::DIRECT3D9 || GetGPUBackend() == GPUBackend::DIRECT3D11) { flags |= OutputFlags::POSITION_FLIPPED; } @@ -1654,7 +1695,7 @@ bool FramebufferManagerCommon::NotifyFramebufferCopy(u32 src, u32 dst, int size, WARN_LOG_ONCE(btucpy, G3D, "Memcpy fbo upload %08x -> %08x (size: %x)", src, dst, size); FlushBeforeCopy(); const u8 *srcBase = Memory::GetPointerUnchecked(src); - DrawPixels(dstBuffer, 0, dstY, srcBase, dstBuffer->fb_format, dstBuffer->fb_stride, dstBuffer->width, dstH); + DrawPixels(dstBuffer, 0, dstY, srcBase, dstBuffer->fb_format, dstBuffer->fb_stride, dstBuffer->width, dstH, RASTER_COLOR, "MemcpyFboUpload_DrawPixels"); SetColorUpdated(dstBuffer, skipDrawReason); RebindFramebuffer("RebindFramebuffer - Memcpy fbo upload"); // This is a memcpy, let's still copy just in case. @@ -1700,7 +1741,16 @@ bool FramebufferManagerCommon::FindTransferFramebuffer(u32 basePtr, int stride_p // that of their buffers. Then after matching we try to map the copy to the simplest operation that does // what we need. + // We are only looking at color for now, have not found any block transfers of depth data (although it's plausible). + for (auto vfb : vfbs_) { + // Check for easily detected depth copies for logging purposes. + // Depth copies are not that useful though because you manually need to account for swizzle, so + // not sure if games will use them. + if ((vfb->z_address & 0x3FFFFFFF) == basePtr) { + WARN_LOG_N_TIMES(z_xfer, 5, G3D, "FindTransferFramebuffer: found matching depth buffer, %08x (dest=%d, bpp=%d)", basePtr, (int)destination, bpp); + } + const u32 vfb_address = vfb->fb_address & 0x3FFFFFFF; const u32 vfb_size = ColorBufferByteSize(vfb); @@ -1773,7 +1823,14 @@ bool FramebufferManagerCommon::FindTransferFramebuffer(u32 basePtr, int stride_p // Sort candidates by just recency for now, we might add other. for (size_t i = 0; i < candidates.size(); i++) { const BlockTransferRect *candidate = &candidates[i]; - if (!best || candidate->vfb->colorBindSeq > best->vfb->colorBindSeq) { + + bool better = !best || candidate->vfb->colorBindSeq > best->vfb->colorBindSeq; + if ((candidate->vfb->usageFlags & FB_USAGE_CLUT) && candidate->x_bytes == 0 && candidate->y == 0 && destination) { + // Hack to prioritize copies to clut buffers. + best = candidate; + break; + } + if (better) { best = candidate; } } @@ -2150,9 +2207,10 @@ void FramebufferManagerCommon::NotifyBlockTransferAfter(u32 dstBasePtr, int dstS } if (dstBuffer && !srcBuffer) { - WARN_LOG_ONCE(btu, G3D, "Block transfer upload %08x -> %08x", srcBasePtr, dstBasePtr); + WARN_LOG_ONCE(btu, G3D, "Block transfer upload %08x -> %08x (%dx%d %d,%d bpp=%d)", srcBasePtr, dstBasePtr, width, height, dstX, dstY, bpp); FlushBeforeCopy(); const u8 *srcBase = Memory::GetPointerUnchecked(srcBasePtr) + (srcX + srcY * srcStride) * bpp; + int dstBpp = BufferFormatBytesPerPixel(dstRect.vfb->fb_format); float dstXFactor = (float)bpp / dstBpp; if (dstRect.w_bytes / bpp > dstRect.vfb->width || dstRect.h > dstRect.vfb->height) { @@ -2166,7 +2224,7 @@ void FramebufferManagerCommon::NotifyBlockTransferAfter(u32 dstBasePtr, int dstS // Resizing may change the viewport/etc. gstate_c.Dirty(DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_CULLRANGE); } - DrawPixels(dstRect.vfb, static_cast(dstX * dstXFactor), dstY, srcBase, dstRect.vfb->fb_format, static_cast(srcStride * dstXFactor), static_cast(dstRect.w_bytes / bpp * dstXFactor), dstRect.h); + DrawPixels(dstRect.vfb, static_cast(dstX * dstXFactor), dstY, srcBase, dstRect.vfb->fb_format, static_cast(srcStride * dstXFactor), static_cast(dstRect.w_bytes / bpp * dstXFactor), dstRect.h, RASTER_COLOR, "BlockTransferCopy_DrawPixels"); SetColorUpdated(dstRect.vfb, skipDrawReason); RebindFramebuffer("RebindFramebuffer - NotifyBlockTransferAfter"); } @@ -2706,7 +2764,7 @@ void FramebufferManagerCommon::DrawActiveTexture(float x, float y, float w, floa // Rearrange to strip form. std::swap(coord[2], coord[3]); - draw2D_.DrawStrip2D(nullptr, coord, 4, (flags & DRAWTEX_LINEAR) != 0, Get2DPipeline(DRAW2D_COPY_COLOR)); + draw2D_.DrawStrip2D(nullptr, coord, 4, (flags & DRAWTEX_LINEAR) != 0, Get2DPipeline((flags & DRAWTEX_DEPTH) ? DRAW2D_COPY_DEPTH : DRAW2D_COPY_COLOR)); gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_RASTER_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_TEXTURE_IMAGE | DIRTY_TEXTURE_PARAMS | DIRTY_VERTEXSHADER_STATE | DIRTY_FRAGMENTSHADER_STATE); } @@ -2926,6 +2984,10 @@ static void ApplyKillzoneFramebufferSplit(FramebufferHeuristicParams *params, in margin = true; } } + + // TODO: Implement this for Burnout Dominator. It has to handle self-reads inside + // the margin framebuffer though, so framebuffer copies are still needed, just smaller. + // It uses 0x0080019f (through, float texcoords, ABGR 8888 colors, float positions). } if (margin) { diff --git a/GPU/Common/FramebufferManagerCommon.h b/GPU/Common/FramebufferManagerCommon.h index 905996979b..5ef9159868 100644 --- a/GPU/Common/FramebufferManagerCommon.h +++ b/GPU/Common/FramebufferManagerCommon.h @@ -192,6 +192,7 @@ enum DrawTextureFlags { DRAWTEX_NEAREST = 0, DRAWTEX_LINEAR = 1, DRAWTEX_TO_BACKBUFFER = 8, + DRAWTEX_DEPTH = 16, }; inline DrawTextureFlags operator | (const DrawTextureFlags &lhs, const DrawTextureFlags &rhs) { @@ -327,7 +328,7 @@ public: void DownloadFramebufferForClut(u32 fb_address, u32 loadBytes); void DrawFramebufferToOutput(const u8 *srcPixels, int srcStride, GEBufferFormat srcPixelFormat); - void DrawPixels(VirtualFramebuffer *vfb, int dstX, int dstY, const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height); + void DrawPixels(VirtualFramebuffer *vfb, int dstX, int dstY, const u8 *srcPixels, GEBufferFormat srcPixelFormat, int srcStride, int width, int height, RasterChannel channel, const char *tag); size_t NumVFBs() const { return vfbs_.size(); } @@ -422,8 +423,18 @@ public: VirtualFramebuffer *ResolveFramebufferColorToFormat(VirtualFramebuffer *vfb, GEBufferFormat newFormat); Draw2DPipeline *Get2DPipeline(Draw2DShader shader); + + // If from==to, returns a copy pipeline. Draw2DPipeline *GetReinterpretPipeline(GEBufferFormat from, GEBufferFormat to, float *scaleFactorX); + // Public to be used from the texture cache's depal shenanigans. + void BlitUsingRaster( + Draw::Framebuffer *src, float srcX1, float srcY1, float srcX2, float srcY2, + Draw::Framebuffer *dest, float destX1, float destY1, float destX2, float destY2, + bool linearFilter, + int scaleFactor, // usually unused, except for swizzle... + Draw2DPipeline *pipeline, const char *tag); + protected: virtual void PackFramebufferSync(VirtualFramebuffer *vfb, int x, int y, int w, int h, RasterChannel channel); void SetViewport2D(int x, int y, int w, int h); @@ -441,13 +452,6 @@ protected: // Used by ReadFramebufferToMemory and later framebuffer block copies void BlitFramebuffer(VirtualFramebuffer *dst, int dstX, int dstY, VirtualFramebuffer *src, int srcX, int srcY, int w, int h, int bpp, RasterChannel channel, const char *tag); - void BlitUsingRaster( - Draw::Framebuffer *src, float srcX1, float srcY1, float srcX2, float srcY2, - Draw::Framebuffer *dest, float destX1, float destY1, float destX2, float destY2, - bool linearFilter, - int scaleFactor, // usually unused, except for swizzle... - Draw2DPipeline *pipeline, const char *tag); - void CopyFramebufferForColorTexture(VirtualFramebuffer *dst, VirtualFramebuffer *src, int flags); void EstimateDrawingSize(u32 fb_address, int fb_stride, GEBufferFormat fb_format, int viewport_width, int viewport_height, int region_width, int region_height, int scissor_width, int scissor_height, int &drawing_width, int &drawing_height); diff --git a/GPU/Common/GPUStateUtils.cpp b/GPU/Common/GPUStateUtils.cpp index 7690156d9d..fb90850cb9 100644 --- a/GPU/Common/GPUStateUtils.cpp +++ b/GPU/Common/GPUStateUtils.cpp @@ -856,7 +856,7 @@ static inline bool blendColorSimilar(uint32_t a, uint32_t b, int margin = 25) { // Try to simulate some common logic ops by using blend, if needed. // The shader might also need modification, the below function SimulateLogicOpShaderTypeIfNeeded // takes care of that. -static void SimulateLogicOpIfNeeded(BlendFactor &srcBlend, BlendFactor &dstBlend, BlendEq &blendEq) { +static bool SimulateLogicOpIfNeeded(BlendFactor &srcBlend, BlendFactor &dstBlend, BlendEq &blendEq) { // Note: our shader solution applies logic ops BEFORE blending, not correctly after. // This is however fine for the most common ones, like CLEAR/NOOP/SET, etc. if (!gstate_c.Supports(GPU_SUPPORTS_LOGIC_OP)) { @@ -866,7 +866,7 @@ static void SimulateLogicOpIfNeeded(BlendFactor &srcBlend, BlendFactor &dstBlend srcBlend = BlendFactor::ZERO; dstBlend = BlendFactor::ZERO; blendEq = BlendEq::ADD; - break; + return true; case GE_LOGIC_AND: case GE_LOGIC_AND_REVERSE: WARN_LOG_REPORT_ONCE(d3dLogicOpAnd, G3D, "Unsupported AND logic op: %x", gstate.getLogicOp()); @@ -889,21 +889,23 @@ static void SimulateLogicOpIfNeeded(BlendFactor &srcBlend, BlendFactor &dstBlend dstBlend = BlendFactor::ONE; blendEq = BlendEq::SUBTRACT; WARN_LOG_REPORT_ONCE(d3dLogicOpInverted, G3D, "Attempted inverse for logic op: %x", gstate.getLogicOp()); - break; + return true; case GE_LOGIC_NOOP: srcBlend = BlendFactor::ZERO; dstBlend = BlendFactor::ONE; blendEq = BlendEq::ADD; - break; + return true; case GE_LOGIC_XOR: WARN_LOG_REPORT_ONCE(d3dLogicOpOrXor, G3D, "Unsupported XOR logic op: %x", gstate.getLogicOp()); break; case GE_LOGIC_OR: case GE_LOGIC_OR_INVERTED: // Inverted in shader. + srcBlend = BlendFactor::ONE; dstBlend = BlendFactor::ONE; + blendEq = BlendEq::ADD; WARN_LOG_REPORT_ONCE(d3dLogicOpOr, G3D, "Attempted or for logic op: %x", gstate.getLogicOp()); - break; + return true; case GE_LOGIC_OR_REVERSE: WARN_LOG_REPORT_ONCE(d3dLogicOpOrReverse, G3D, "Unsupported OR REVERSE logic op: %x", gstate.getLogicOp()); break; @@ -912,10 +914,12 @@ static void SimulateLogicOpIfNeeded(BlendFactor &srcBlend, BlendFactor &dstBlend dstBlend = BlendFactor::ONE; blendEq = BlendEq::ADD; WARN_LOG_REPORT_ONCE(d3dLogicOpSet, G3D, "Attempted set for logic op: %x", gstate.getLogicOp()); - break; + return true; } } } + + return false; } // Choose the shader part of the above logic op fallback simulation. @@ -950,7 +954,6 @@ void ApplyStencilReplaceAndLogicOpIgnoreBlend(ReplaceAlphaType replaceAlphaWithS BlendFactor srcBlend = BlendFactor::ONE; BlendFactor dstBlend = BlendFactor::ZERO; BlendEq blendEq = BlendEq::ADD; - SimulateLogicOpIfNeeded(srcBlend, dstBlend, blendEq); // We're not blending, but we may still want to "blend" for stencil. // This is only useful for INCR/DECR/INVERT. Others can write directly. @@ -1252,11 +1255,6 @@ static void ConvertBlendState(GenericBlendState &blendState, bool forceReplaceBl colorEq = eqLookupNoMinMax[blendFuncEq]; } - // Attempt to apply simulated logic ops, if any and if needed. - if (!forceReplaceBlend) { - SimulateLogicOpIfNeeded(glBlendFuncA, glBlendFuncB, colorEq); - } - // The stencil-to-alpha in fragment shader doesn't apply here (blending is enabled), and we shouldn't // do any blending in the alpha channel as that doesn't seem to happen on PSP. So, we attempt to // apply the stencil to the alpha, since that's what should be stored. @@ -1581,5 +1579,20 @@ void ComputedPipelineState::Convert(bool shaderBitOpsSuppported) { if (blendState.applyFramebufferRead || logicState.applyFramebufferRead) { maskState.ConvertToShaderBlend(); logicState.ConvertToShaderBlend(); + } else { + // If it isn't a read, we may need to change blending to apply the logic op. + logicState.ApplyToBlendState(blendState); + } +} + +void GenericLogicState::ApplyToBlendState(GenericBlendState &blendState) { + if (SimulateLogicOpIfNeeded(blendState.srcColor, blendState.dstColor, blendState.eqColor)) { + if (!blendState.blendEnabled) { + // If it wasn't turned on, make sure it is now. + blendState.blendEnabled = true; + blendState.srcAlpha = BlendFactor::ONE; + blendState.dstAlpha = BlendFactor::ZERO; + blendState.eqAlpha = BlendEq::ADD; + } } } diff --git a/GPU/Common/GPUStateUtils.h b/GPU/Common/GPUStateUtils.h index bc0c9ae7b1..038091f7d8 100644 --- a/GPU/Common/GPUStateUtils.h +++ b/GPU/Common/GPUStateUtils.h @@ -226,6 +226,7 @@ struct GenericLogicState { // Hardware and shader generation GELogicOp logicOp; + void ApplyToBlendState(GenericBlendState &blendState); void ConvertToShaderBlend() { if (logicOp != GE_LOGIC_COPY) { logicOpEnabled = false; diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index f873024c39..7ca42ccba6 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -180,7 +180,7 @@ std::string FragmentShaderDesc(const FShaderID &id) { if (id.Bit(FS_BIT_COLOR_DOUBLE)) desc << "2x "; if (id.Bit(FS_BIT_FLATSHADE)) desc << "Flat "; if (id.Bit(FS_BIT_BGRA_TEXTURE)) desc << "BGRA "; - switch ((ShaderDepalMode)id.Bit(FS_BIT_SHADER_DEPAL_MODE)) { + switch ((ShaderDepalMode)id.Bits(FS_BIT_SHADER_DEPAL_MODE, 2)) { case ShaderDepalMode::OFF: break; case ShaderDepalMode::NORMAL: desc << "Depal "; break; case ShaderDepalMode::SMOOTHED: desc << "SmoothDepal "; break; diff --git a/GPU/Common/ShaderUniforms.h b/GPU/Common/ShaderUniforms.h index 691835a262..f64fc3417b 100644 --- a/GPU/Common/ShaderUniforms.h +++ b/GPU/Common/ShaderUniforms.h @@ -36,14 +36,14 @@ struct UB_VS_FS_Base { uint32_t spline_counts; uint32_t depal_mask_shift_off_fmt; // 4 params packed into one. uint32_t colorWriteMask; float mipBias; // Fragment data - float fogColor[4]; - float texEnvColor[4]; + float fogColor[4]; // .w is unused + float texEnvColor[4]; // .w is unused int alphaColorRef[4]; int colorTestMask[4]; - float blendFixA[4]; - float blendFixB[4]; + float blendFixA[4]; // .w is unused + float blendFixB[4]; // .w is unused float texClamp[4]; - float texClampOffset[4]; + float texClampOffset[4]; // .zw are unused }; static const char *ub_baseStr = diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index e4b463360d..66d70f7c02 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -377,13 +377,22 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { } bool hasClut = gstate.isTextureFormatIndexed(); + bool hasClutGPU = false; u32 cluthash; if (hasClut) { - if (clutLastFormat_ != gstate.clutformat) { - // We update here because the clut format can be specified after the load. - UpdateCurrentClut(gstate.getClutPaletteFormat(), gstate.getClutIndexStartPos(), gstate.isClutIndexSimple()); + if (clutRenderAddress_ != 0xFFFFFFFF) { + gstate_c.curTextureXOffset = 0.0f; + gstate_c.curTextureYOffset = 0.0f; + hasClutGPU = true; + cluthash = 0; // Or should we use some other marker value? + } else { + if (clutLastFormat_ != gstate.clutformat) { + // We update here because the clut format can be specified after the load. + // TODO: Unify this as far as possible (I think only GLES backend really needs its own implementation due to different component order). + UpdateCurrentClut(gstate.getClutPaletteFormat(), gstate.getClutIndexStartPos(), gstate.isClutIndexSimple()); + } + cluthash = clutHash_ ^ gstate.clutformat; } - cluthash = clutHash_ ^ gstate.clutformat; } else { cluthash = 0; } @@ -412,6 +421,13 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { bool match = entry->Matches(dim, texFormat, maxLevel); const char *reason = "different params"; + // Check for dynamic CLUT status + if (((entry->status & TexCacheEntry::STATUS_CLUT_GPU) != 0) != hasClutGPU) { + // Need to recreate, suddenly a CLUT GPU texture was used without it, or vice versa. + // I think this can only happen on a clut hash collision with the marker value, so highly unlikely. + match = false; + } + // Check for FBO changes. if (entry->status & TexCacheEntry::STATUS_FRAMEBUFFER_OVERLAP) { // Fall through to the end where we'll delete the entry if there's a framebuffer. @@ -553,10 +569,6 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { entry = new TexCacheEntry{}; cache_[cachekey].reset(entry); - if (hasClut && clutRenderAddress_ != 0xFFFFFFFF) { - WARN_LOG_REPORT_ONCE(clutUseRender, G3D, "Using texture with rendered CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat()); - } - if (PPGeIsFontTextureAddress(texaddr)) { // It's the builtin font texture. entry->status = TexCacheEntry::STATUS_RELIABLE; @@ -566,6 +578,11 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { entry->status = TexCacheEntry::STATUS_UNRELIABLE; } + if (hasClutGPU) { + WARN_LOG_REPORT_ONCE(clutUseRender, G3D, "Using texture with dynamic CLUT: texfmt=%d, clutfmt=%d", gstate.getTextureFormat(), gstate.getClutPaletteFormat()); + entry->status |= TexCacheEntry::STATUS_CLUT_GPU; + } + if (hasClut && clutRenderAddress_ == 0xFFFFFFFF) { const u64 cachekeyMin = (u64)(texaddr & 0x3FFFFFFF) << 32; const u64 cachekeyMax = cachekeyMin + (1ULL << 32); @@ -947,12 +964,13 @@ bool TextureCacheCommon::MatchFramebuffer( return false; } - // Check works for D16 too (???) + // Check works for D16 too. const bool matchingClutFormat = (fb_format == GE_FORMAT_DEPTH16 && entry.format == GE_TFMT_CLUT16) || (fb_format == GE_FORMAT_DEPTH16 && entry.format == GE_TFMT_5650) || (fb_format == GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT32) || - (fb_format != GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT16); + (fb_format != GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT16) || + (fb_format == GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT8); const int texBitsPerPixel = std::max(1U, (u32)textureBitsPerPixel[entry.format]); const int byteOffset = texaddr - addr; @@ -991,6 +1009,10 @@ bool TextureCacheCommon::MatchFramebuffer( if (fb_stride_in_bytes != tex_stride_in_bytes) { // Probably irrelevant. Although, as we shall see soon, there are exceptions. + // Burnout Dominator lens flare trick special case. + if (fb_format == GE_FORMAT_8888 && entry.format == GE_TFMT_CLUT8 && texWidth == 4 && texHeight == 1) { + return true; + } return false; } @@ -1017,8 +1039,8 @@ bool TextureCacheCommon::MatchFramebuffer( return true; } } else { - WARN_LOG_ONCE(diffFormat2, G3D, "Ignoring possible texturing from framebuffer with incompatible format %s != %s at %08x", - GeTextureFormatToString(entry.format), GeBufferFormatToString(fb_format), fb_address); + WARN_LOG_ONCE(diffFormat2, G3D, "Ignoring possible texturing from framebuffer with incompatible format %s != %s at %08x (+%dx%d)", + GeTextureFormatToString(entry.format), GeBufferFormatToString(fb_format), fb_address, matchInfo->xOffset, matchInfo->yOffset); return false; } } @@ -1168,33 +1190,71 @@ void TextureCacheCommon::LoadClut(u32 clutAddr, u32 loadBytes) { if (Memory::IsValidAddress(clutAddr)) { if (Memory::IsVRAMAddress(clutAddr)) { // Clear the uncached bit, etc. to match framebuffers. - const u32 clutFramebufAddr = clutAddr & 0x3FFFFFFF; - const u32 clutFramebufEnd = clutFramebufAddr + loadBytes; + const u32 clutLoadAddr = clutAddr & 0x3FFFFFFF; + const u32 clutLoadEnd = clutLoadAddr + loadBytes; static const u32 MAX_CLUT_OFFSET = 4096; clutRenderOffset_ = MAX_CLUT_OFFSET; const std::vector &framebuffers = framebufferManager_->Framebuffers(); + + VirtualFramebuffer *chosenFramebuffer = nullptr; for (VirtualFramebuffer *framebuffer : framebuffers) { const u32 fb_address = framebuffer->fb_address & 0x3FFFFFFF; - const u32 bpp = BufferFormatBytesPerPixel(framebuffer->fb_format); - u32 offset = clutFramebufAddr - fb_address; + const u32 fb_bpp = BufferFormatBytesPerPixel(framebuffer->fb_format); + int offset = clutLoadAddr - fb_address; - // Is this inside the framebuffer at all? - bool matchRange = fb_address + framebuffer->fb_stride * bpp > clutFramebufAddr && fb_address < clutFramebufEnd; - // And is it inside the rendered area? Sometimes games pack data outside. - bool matchRegion = ((offset / bpp) % framebuffer->fb_stride) < framebuffer->width; - if (matchRange && matchRegion && offset < clutRenderOffset_) { - WARN_LOG_N_TIMES(clutfb, 5, G3D, "Detected LoadCLUT(%d bytes) from framebuffer %08x (%s), byte offset %d", loadBytes, fb_address, GeBufferFormatToString(framebuffer->fb_format), offset); - framebuffer->last_frame_clut = gpuStats.numFlips; - framebuffer->usageFlags |= FB_USAGE_CLUT; - clutRenderAddress_ = framebuffer->fb_address; - clutRenderOffset_ = offset; - if (offset == 0) { - break; + // Is this inside the framebuffer at all? Note that we only check the first line here, this should + // be changed. + bool matchRange = offset >= 0 && offset < (int)(framebuffer->fb_stride * fb_bpp); + if (matchRange) { + // And is it inside the rendered area? Sometimes games pack data in the margin between width and stride. + // If the framebuffer width was detected as 512, we're gonna assume it's really 480. + int fbMatchWidth = framebuffer->width; + if (fbMatchWidth == 512) { + fbMatchWidth = 480; + } + bool inMargin = ((offset / fb_bpp) % framebuffer->fb_stride) == fbMatchWidth; + + // The offset check here means, in the context of the loop, that we'll pick + // the framebuffer with the smallest offset. This is yet another framebuffer matching + // loop with its own rules, eventually we'll probably want to do something + // more systematic. + if (matchRange && !inMargin && offset < (int)clutRenderOffset_) { + WARN_LOG_N_TIMES(clutfb, 5, G3D, "Detected LoadCLUT(%d bytes) from framebuffer %08x (%s), byte offset %d", loadBytes, fb_address, GeBufferFormatToString(framebuffer->fb_format), offset); + framebuffer->last_frame_clut = gpuStats.numFlips; + framebuffer->usageFlags |= FB_USAGE_CLUT; + clutRenderAddress_ = framebuffer->fb_address; + clutRenderOffset_ = (u32)offset; + chosenFramebuffer = framebuffer; + if (offset == 0) { + // Not gonna find a better match according to the smallest-offset rule, so we'll go with this one. + break; + } } } } + if (chosenFramebuffer) { + if (!dynamicClutTemp_) { + Draw::FramebufferDesc desc{}; + desc.width = 512; + desc.height = 1; + desc.depth = 1; + desc.z_stencil = false; + desc.numColorAttachments = 1; + desc.tag = "dynamic_clut"; + dynamicClutFbo_ = draw_->CreateFramebuffer(desc); + desc.tag = "dynamic_clut_temp"; + dynamicClutTemp_ = draw_->CreateFramebuffer(desc); + } + + // Download the pixels to our temp clut, scaling down if needed. + framebufferManager_->BlitUsingRaster( + chosenFramebuffer->fbo, 0.0f, 0.0f, 512.0f * chosenFramebuffer->renderScaleFactor, 1.0f, + dynamicClutTemp_, 0.0f, 0.0f, 512.0f, 1.0f, + false, 1.0f, framebufferManager_->Get2DPipeline(DRAW2D_COPY_COLOR), "copy_clut_to_temp"); + clutRenderFormat_ = chosenFramebuffer->fb_format; + } NotifyMemInfo(MemBlockFlags::ALLOC, clutAddr, loadBytes, "CLUT"); } @@ -1471,10 +1531,29 @@ inline u32 TfmtRawToFullAlpha(GETextureFormat fmt) { } } -CheckAlphaResult TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, GETextureFormat format, GEPaletteFormat clutformat, uint32_t texaddr, int level, int bufw, bool reverseColors, bool expandTo32bit) { +// Used for converting CLUT4 to CLUT8. +// Could SIMD or whatever, though will hardly be a bottleneck. +static void Expand4To8Bits(u8 *dest, const u8 *src, int srcWidth) { + for (int i = 0; i < (srcWidth + 1) / 2; i++) { + u8 lower = src[i] & 0xF; + u8 upper = src[i] >> 4; + dest[i * 2] = lower; + dest[i * 2 + 1] = upper; + } +} + +CheckAlphaResult TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, GETextureFormat format, GEPaletteFormat clutformat, uint32_t texaddr, int level, int bufw, TexDecodeFlags flags) { u32 alphaSum = 0xFFFFFFFF; u32 fullAlphaMask = 0x0; + bool expandTo32bit = (flags & TexDecodeFlags::EXPAND32) != 0; + bool reverseColors = (flags & TexDecodeFlags::REVERSE_COLORS) != 0; + bool toClut8 = (flags & TexDecodeFlags::TO_CLUT8) != 0; + + if (toClut8 && format != GE_TFMT_CLUT8 && format != GE_TFMT_CLUT4) { + _dbg_assert_(false); + } + bool swizzled = gstate.isTextureSwizzled(); if ((texaddr & 0x00600000) != 0 && Memory::IsVRAMAddress(texaddr)) { // This means it's in a mirror, possibly a swizzled mirror. Let's report. @@ -1508,6 +1587,15 @@ CheckAlphaResult TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, G texptr = (u8 *)tmpTexBuf32_.data(); } + if (toClut8) { + // We just need to expand from 4 to 8 bits. + for (int y = 0; y < h; ++y) { + Expand4To8Bits((u8 *)out + outPitch * y, texptr + (bufw * y) / 2, w); + } + // We can't know anything about alpha. + return CHECKALPHA_ANY; + } + switch (clutformat) { case GE_CMODE_16BIT_BGR5650: case GE_CMODE_16BIT_ABGR5551: @@ -1570,6 +1658,19 @@ CheckAlphaResult TextureCacheCommon::DecodeTextureLevel(u8 *out, int outPitch, G break; case GE_TFMT_CLUT8: + if (toClut8) { + if (gstate.isTextureSwizzled()) { + tmpTexBuf32_.resize(bufw * ((h + 7) & ~7)); + UnswizzleFromMem(tmpTexBuf32_.data(), bufw, texptr, bufw, h, 1); + texptr = (u8 *)tmpTexBuf32_.data(); + } + // After deswizzling, we are in the correct format and can just copy. + for (int y = 0; y < h; ++y) { + memcpy((u8 *)out + outPitch * y, texptr + (bufw * y), w); + } + // We can't know anything about alpha. + return CHECKALPHA_ANY; + } return ReadIndexedTex(out, outPitch, level, texptr, 1, bufw, reverseColors, expandTo32bit); case GE_TFMT_CLUT16: @@ -1855,10 +1956,18 @@ void TextureCacheCommon::ApplyTexture() { InvalidateLastTexture(); } - entry->lastFrame = gpuStats.numFlips; - BindTexture(entry); - gstate_c.SetTextureFullAlpha(entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL); - gstate_c.SetTextureIs3D((entry->status & TexCacheEntry::STATUS_3D) != 0); + if (entry->status & TexCacheEntry::STATUS_CLUT_GPU) { + // Special process. + ApplyTextureDepal(entry); + entry->lastFrame = gpuStats.numFlips; + gstate_c.SetTextureFullAlpha(false); + gstate_c.SetTextureIs3D(false); + } else { + entry->lastFrame = gpuStats.numFlips; + BindTexture(entry); + gstate_c.SetTextureFullAlpha(entry->GetAlphaStatus() == TexCacheEntry::STATUS_ALPHA_FULL); + gstate_c.SetTextureIs3D((entry->status & TexCacheEntry::STATUS_3D) != 0); + } } static bool CanDepalettize(GETextureFormat texFormat, GEBufferFormat bufferFormat) { @@ -1873,7 +1982,7 @@ static bool CanDepalettize(GETextureFormat texFormat, GEBufferFormat bufferForma } break; case GE_FORMAT_8888: - if (texFormat == GE_TFMT_CLUT32) { + if (texFormat == GE_TFMT_CLUT32 || texFormat == GE_TFMT_CLUT8) { // clut8 takes a special depal mode. return true; } break; @@ -1915,7 +2024,6 @@ static bool CanUseSmoothDepal(const GPUgstate &gstate, GEBufferFormat framebuffe return false; } - void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, GETextureFormat texFormat, RasterChannel channel) { Draw2DPipeline *textureShader = nullptr; uint32_t clutMode = gstate.clutformat & 0xFFFFFF; @@ -1961,9 +2069,17 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer samplerKey.mipEnable = false; ApplySamplingParams(samplerKey); + ShaderDepalMode mode = ShaderDepalMode::NORMAL; + if (texFormat == GE_TFMT_CLUT8 && framebuffer->fb_format == GE_FORMAT_8888) { + mode = ShaderDepalMode::CLUT8_8888; + smoothedDepal = false; // just in case + } else if (smoothedDepal) { + mode = ShaderDepalMode::SMOOTHED; + } + // Since we started/ended render passes, might need these. gstate_c.Dirty(DIRTY_DEPAL); - gstate_c.SetUseShaderDepal(smoothedDepal ? ShaderDepalMode::SMOOTHED : ShaderDepalMode::NORMAL); + gstate_c.SetUseShaderDepal(mode); gstate_c.depalFramebufferFormat = framebuffer->fb_format; const u32 bytesPerColor = clutFormat == GE_CMODE_32BIT_ABGR8888 ? sizeof(u32) : sizeof(u16); @@ -2063,6 +2179,98 @@ void TextureCacheCommon::ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE); } +// Applies depal to a normal (non-framebuffer) texture, pre-decoded to CLUT8 format. +void TextureCacheCommon::ApplyTextureDepal(TexCacheEntry *entry) { + uint32_t clutMode = gstate.clutformat & 0xFFFFFF; + + switch (entry->format) { + case GE_TFMT_CLUT4: + case GE_TFMT_CLUT8: + break; // These are OK + default: + _dbg_assert_(false); + return; + } + + const GEPaletteFormat clutFormat = gstate.getClutPaletteFormat(); + u32 depthUpperBits = 0; + + // The CLUT texture is dynamic, it's the framebuffer pointed to by clutRenderAddress. + // Instead of texturing directly from that, we copy to a temporary CLUT texture. + GEBufferFormat expectedCLUTBufferFormat = (GEBufferFormat)clutFormat; // All entries from clutFormat correspond directly to buffer formats. + + // OK, figure out what format we want our framebuffer in, so it can be reinterpreted if needed. + // If no reinterpretation is needed, we'll automatically just get a copy shader. + float scaleFactorX = 1.0f; + Draw2DPipeline *reinterpret = framebufferManager_->GetReinterpretPipeline(clutRenderFormat_, expectedCLUTBufferFormat, &scaleFactorX); + framebufferManager_->BlitUsingRaster( + dynamicClutTemp_, 0.0f, 0.0f, 512.0f, 1.0f, dynamicClutFbo_, 0.0f, 0.0f, scaleFactorX * 512.0f, 1.0f, false, 1.0f, reinterpret, "reinterpret_clut"); + + Draw2DPipeline *textureShader = textureShaderCache_->GetDepalettizeShader(clutMode, GE_TFMT_CLUT8, GE_FORMAT_CLUT8, false, 0); + gstate_c.SetUseShaderDepal(ShaderDepalMode::OFF); + + int texWidth = gstate.getTextureWidth(0); + int texHeight = gstate.getTextureHeight(0); + + // If min is not < max, then we don't have values (wasn't set during decode.) + const KnownVertexBounds &bounds = gstate_c.vertBounds; + float u1 = 0.0f; + float v1 = 0.0f; + float u2 = texWidth; + float v2 = texHeight; + if (bounds.minV < bounds.maxV) { + u1 = (bounds.minU + gstate_c.curTextureXOffset) * texWidth; + v1 = (bounds.minV + gstate_c.curTextureYOffset) * texHeight; + u2 = (bounds.maxU + gstate_c.curTextureXOffset) * texWidth; + v2 = (bounds.maxV + gstate_c.curTextureYOffset) * texHeight; + // We need to reapply the texture next time since we cropped UV. + gstate_c.Dirty(DIRTY_TEXTURE_PARAMS); + } + + Draw::Framebuffer *depalFBO = framebufferManager_->GetTempFBO(TempFBO::DEPAL, texWidth, texHeight); + draw_->BindTexture(0, nullptr); + draw_->BindTexture(1, nullptr); + draw_->BindFramebufferAsRenderTarget(depalFBO, { Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE, Draw::RPAction::DONT_CARE }, "Depal"); + draw_->InvalidateFramebuffer(Draw::FB_INVALIDATION_STORE, Draw::FB_DEPTH_BIT | Draw::FB_STENCIL_BIT); + draw_->SetScissorRect(u1, v1, u2 - u1, v2 - v1); + Draw::Viewport vp{ 0.0f, 0.0f, (float)texWidth, (float)texHeight, 0.0f, 1.0f }; + draw_->SetViewports(1, &vp); + + draw_->BindNativeTexture(0, GetNativeTextureView(entry)); + draw_->BindFramebufferAsTexture(dynamicClutFbo_, 1, Draw::FB_COLOR_BIT, 0); + Draw::SamplerState *nearest = textureShaderCache_->GetSampler(false); + Draw::SamplerState *clutSampler = textureShaderCache_->GetSampler(false); + draw_->BindSamplerStates(0, 1, &nearest); + draw_->BindSamplerStates(1, 1, &clutSampler); + + draw2D_->Blit(textureShader, u1, v1, u2, v2, u1, v1, u2, v2, texWidth, texHeight, texWidth, texHeight, false, 1); + + gpuStats.numDepal++; + + gstate_c.curTextureWidth = texWidth; + + draw_->BindTexture(0, nullptr); + framebufferManager_->RebindFramebuffer("ApplyTextureFramebuffer"); + + draw_->BindFramebufferAsTexture(depalFBO, 0, Draw::FB_COLOR_BIT, 0); + BoundFramebufferTexture(); + + const u32 bytesPerColor = clutFormat == GE_CMODE_32BIT_ABGR8888 ? sizeof(u32) : sizeof(u16); + const u32 clutTotalColors = clutMaxBytes_ / bytesPerColor; + + // We don't know about alpha at all. + gstate_c.SetTextureFullAlpha(false); + + draw_->InvalidateCachedState(); + shaderManager_->DirtyLastShader(); + + SamplerCacheKey samplerKey = GetFramebufferSamplingParams(texWidth, texHeight); + ApplySamplingParams(samplerKey); + + // Since we started/ended render passes, might need these. + gstate_c.Dirty(DIRTY_BLEND_STATE | DIRTY_DEPTHSTENCIL_STATE | DIRTY_RASTER_STATE | DIRTY_VIEWPORTSCISSOR_STATE); +} + void TextureCacheCommon::Clear(bool delete_them) { textureShaderCache_->Clear(); @@ -2082,6 +2290,15 @@ void TextureCacheCommon::Clear(bool delete_them) { secondCacheSizeEstimate_ = 0; } videos_.clear(); + + if (dynamicClutFbo_) { + dynamicClutFbo_->Release(); + dynamicClutFbo_ = nullptr; + } + if (dynamicClutTemp_) { + dynamicClutTemp_->Release(); + dynamicClutTemp_ = nullptr; + } } void TextureCacheCommon::DeleteTexture(TexCache::iterator it) { @@ -2448,6 +2665,21 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt plan.maxPossibleLevels = log2i(std::min(plan.createW, plan.createH)) + 1; } + if (entry->status & TexCacheEntry::TexStatus::STATUS_CLUT_GPU) { + _dbg_assert_(entry->format == GE_TFMT_CLUT4 || entry->format == GE_TFMT_CLUT8); + plan.decodeToClut8 = true; + // We only support 1 mip level when doing CLUT on GPU for now. + // Supporting more would be possible, just not very interesting until we need it. + plan.levelsToCreate = 1; + plan.levelsToLoad = 1; + plan.maxPossibleLevels = 1; + plan.scaleFactor = 1; + plan.saveTexture = false; // Can't yet save these properly. + // TODO: Also forcibly disable replacement, or check that the replacement is a 8-bit paletted texture. + } else { + plan.decodeToClut8 = false; + } + if (plan.levelsToCreate == 1) { entry->status |= TexCacheEntry::STATUS_NO_MIPS; } else { @@ -2459,7 +2691,7 @@ bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEnt return true; } -void TextureCacheCommon::LoadTextureLevel(TexCacheEntry &entry, uint8_t *data, int stride, ReplacedTexture &replaced, int srcLevel, int scaleFactor, Draw::DataFormat dstFmt, bool reverseColors) { +void TextureCacheCommon::LoadTextureLevel(TexCacheEntry &entry, uint8_t *data, int stride, ReplacedTexture &replaced, int srcLevel, int scaleFactor, Draw::DataFormat dstFmt, TexDecodeFlags texDecFlags) { int w = gstate.getTextureWidth(srcLevel); int h = gstate.getTextureHeight(srcLevel); @@ -2486,9 +2718,14 @@ void TextureCacheCommon::LoadTextureLevel(TexCacheEntry &entry, uint8_t *data, i decPitch = stride; } - bool expand32 = !gstate_c.Supports(GPU_SUPPORTS_16BIT_FORMATS) || dstFmt == Draw::DataFormat::R8G8B8A8_UNORM; + if (!gstate_c.Supports(GPU_SUPPORTS_16BIT_FORMATS) || dstFmt == Draw::DataFormat::R8G8B8A8_UNORM) { + texDecFlags |= TexDecodeFlags::EXPAND32; + } + if (entry.status & TexCacheEntry::STATUS_CLUT_GPU) { + texDecFlags |= TexDecodeFlags::TO_CLUT8; + } - CheckAlphaResult alphaResult = DecodeTextureLevel((u8 *)pixelData, decPitch, tfmt, clutformat, texaddr, srcLevel, bufw, reverseColors, expand32); + CheckAlphaResult alphaResult = DecodeTextureLevel((u8 *)pixelData, decPitch, tfmt, clutformat, texaddr, srcLevel, bufw, texDecFlags); entry.SetAlphaStatus(alphaResult, srcLevel); if (scaleFactor > 1) { diff --git a/GPU/Common/TextureCacheCommon.h b/GPU/Common/TextureCacheCommon.h index 82c79773a3..9922e9070a 100644 --- a/GPU/Common/TextureCacheCommon.h +++ b/GPU/Common/TextureCacheCommon.h @@ -50,6 +50,13 @@ struct VirtualFramebuffer; class TextureReplacer; class ShaderManagerCommon; +enum class TexDecodeFlags { + EXPAND32 = 1, + REVERSE_COLORS = 2, + TO_CLUT8 = 4, +}; +ENUM_CLASS_BITOPS(TexDecodeFlags); + namespace Draw { class DrawContext; class Texture; @@ -102,6 +109,8 @@ struct TextureDefinition { // NOTE: These only handle textures loaded directly from PSP memory contents. // Framebuffer textures do not have entries, we bind the framebuffers directly. +// At one point we might merge the concepts of framebuffers and textures, but that +// moment is far away. struct TexCacheEntry { ~TexCacheEntry() { if (texturePtr || textureName || vkTex) @@ -138,6 +147,8 @@ struct TexCacheEntry { STATUS_FORCE_REBUILD = 0x2000, STATUS_3D = 0x4000, + + STATUS_CLUT_GPU = 0x8000, }; // Status, but int so we can zero initialize. @@ -275,6 +286,9 @@ struct BuildTexturePlan { bool replaceValid; bool saveTexture; + // TODO: Expand32 should probably also be decided in PrepareBuildTexture. + bool decodeToClut8; + void GetMipSize(int level, int *w, int *h) const { if (replaceValid) { replaced->GetSize(level, *w, *h); @@ -337,6 +351,7 @@ public: virtual bool GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level) { return false; } protected: + virtual void *GetNativeTextureView(const TexCacheEntry *entry) = 0; bool PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEntry *entry); virtual void BindTexture(TexCacheEntry *entry) = 0; @@ -346,6 +361,7 @@ protected: void Decimate(bool forcePressure = false); void ApplyTextureFramebuffer(VirtualFramebuffer *framebuffer, GETextureFormat texFormat, RasterChannel channel); + void ApplyTextureDepal(TexCacheEntry *entry); void HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete); virtual void BuildTexture(TexCacheEntry *const entry) = 0; @@ -354,13 +370,13 @@ protected: virtual void BindAsClutTexture(Draw::Texture *tex, bool smooth) {} - CheckAlphaResult DecodeTextureLevel(u8 *out, int outPitch, GETextureFormat format, GEPaletteFormat clutformat, uint32_t texaddr, int level, int bufw, bool reverseColors, bool expandTo32Bit); + CheckAlphaResult DecodeTextureLevel(u8 *out, int outPitch, GETextureFormat format, GEPaletteFormat clutformat, uint32_t texaddr, int level, int bufw, TexDecodeFlags flags); void UnswizzleFromMem(u32 *dest, u32 destPitch, const u8 *texptr, u32 bufw, u32 height, u32 bytesPerPixel); CheckAlphaResult ReadIndexedTex(u8 *out, int outPitch, int level, const u8 *texptr, int bytesPerIndex, int bufw, bool reverseColors, bool expandTo32Bit); ReplacedTexture &FindReplacement(TexCacheEntry *entry, int &w, int &h, int &d); // Return value is mapData normally, but could be another buffer allocated with AllocateAlignedMemory. - void LoadTextureLevel(TexCacheEntry &entry, uint8_t *mapData, int mapRowPitch, ReplacedTexture &replaced, int srcLevel, int scaleFactor, Draw::DataFormat dstFmt, bool reverseColors); + void LoadTextureLevel(TexCacheEntry &entry, uint8_t *mapData, int mapRowPitch, ReplacedTexture &replaced, int srcLevel, int scaleFactor, Draw::DataFormat dstFmt, TexDecodeFlags texDecFlags); template inline const T *GetCurrentClut() { @@ -470,10 +486,16 @@ protected: u32 clutMaxBytes_ = 0; u32 clutRenderAddress_ = 0xFFFFFFFF; u32 clutRenderOffset_; + GEBufferFormat clutRenderFormat_; + // True if the clut is just alpha values in the same order (RGBA4444-bit only.) bool clutAlphaLinear_ = false; u16 clutAlphaLinearColor_; + // Facilities for GPU depal of static textures. + Draw::Framebuffer *dynamicClutTemp_ = nullptr; + Draw::Framebuffer *dynamicClutFbo_ = nullptr; + int standardScaleFactor_; int shaderScaleFactor_ = 0; diff --git a/GPU/Common/TextureShaderCommon.cpp b/GPU/Common/TextureShaderCommon.cpp index f364016e72..1c67818a85 100644 --- a/GPU/Common/TextureShaderCommon.cpp +++ b/GPU/Common/TextureShaderCommon.cpp @@ -102,7 +102,7 @@ ClutTexture TextureShaderCache::GetClutTexture(GEPaletteFormat clutFormat, const int lastA = 0; int rampLength = 0; - // Quick check for how many continouosly growing entries we have at the start. + // Quick check for how many continuously growing entries we have at the start. // Bilinearly filtering CLUTs only really makes sense for this kind of ramp. for (int i = 0; i < maxClutEntries; i++) { rampLength = i; diff --git a/GPU/D3D11/TextureCacheD3D11.cpp b/GPU/D3D11/TextureCacheD3D11.cpp index 3a7ead0829..0930aec655 100644 --- a/GPU/D3D11/TextureCacheD3D11.cpp +++ b/GPU/D3D11/TextureCacheD3D11.cpp @@ -65,6 +65,8 @@ Draw::DataFormat FromD3D11Format(u32 fmt) { return Draw::DataFormat::A1R5G5B5_UNORM_PACK16; case DXGI_FORMAT_B5G6R5_UNORM: return Draw::DataFormat::R5G6B5_UNORM_PACK16; + case DXGI_FORMAT_R8_UNORM: + return Draw::DataFormat::R8_UNORM; case DXGI_FORMAT_B8G8R8A8_UNORM: default: return Draw::DataFormat::R8G8B8A8_UNORM; @@ -290,6 +292,8 @@ void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry) { dstFmt = ToDXGIFormat(plan.replaced->Format(plan.baseLevelSrc)); } else if (plan.scaleFactor > 1 || plan.saveTexture) { dstFmt = DXGI_FORMAT_B8G8R8A8_UNORM; + } else if (plan.decodeToClut8) { + dstFmt = DXGI_FORMAT_R8_UNORM; } int levels; @@ -378,7 +382,7 @@ void TextureCacheD3D11::BuildTexture(TexCacheEntry *const entry) { return; } - LoadTextureLevel(*entry, data, stride, *plan.replaced, srcLevel, plan.scaleFactor, texFmt, false); + LoadTextureLevel(*entry, data, stride, *plan.replaced, srcLevel, plan.scaleFactor, texFmt, TexDecodeFlags{}); if (plan.depth == 1) { context_->UpdateSubresource(texture, i, nullptr, data, stride, 0); } else { @@ -525,3 +529,8 @@ bool TextureCacheD3D11::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level stagingCopy->Release(); return true; } + +void *TextureCacheD3D11::GetNativeTextureView(const TexCacheEntry *entry) { + ID3D11ShaderResourceView *textureView = DxView(entry); + return (void *)textureView; +} diff --git a/GPU/D3D11/TextureCacheD3D11.h b/GPU/D3D11/TextureCacheD3D11.h index 03ec04a481..e1be7d3aaf 100644 --- a/GPU/D3D11/TextureCacheD3D11.h +++ b/GPU/D3D11/TextureCacheD3D11.h @@ -61,6 +61,7 @@ protected: void ReleaseTexture(TexCacheEntry *entry, bool delete_them) override; void BindAsClutTexture(Draw::Texture *tex, bool smooth) override; void ApplySamplingParams(const SamplerCacheKey &key) override; + void *GetNativeTextureView(const TexCacheEntry *entry) override; private: DXGI_FORMAT GetDestFormat(GETextureFormat format, GEPaletteFormat clutFormat) const; @@ -72,10 +73,10 @@ private: ID3D11Device *device_; ID3D11DeviceContext *context_; - ID3D11Resource *&DxTex(TexCacheEntry *entry) { + ID3D11Resource *&DxTex(const TexCacheEntry *entry) { return (ID3D11Resource *&)entry->texturePtr; } - ID3D11ShaderResourceView *DxView(TexCacheEntry *entry) { + ID3D11ShaderResourceView *DxView(const TexCacheEntry *entry) { return (ID3D11ShaderResourceView *)entry->textureView; } diff --git a/GPU/Directx9/TextureCacheDX9.cpp b/GPU/Directx9/TextureCacheDX9.cpp index 3a06c3b4f5..3a3feda7f3 100644 --- a/GPU/Directx9/TextureCacheDX9.cpp +++ b/GPU/Directx9/TextureCacheDX9.cpp @@ -290,8 +290,6 @@ void TextureCacheDX9::BuildTexture(TexCacheEntry *const entry) { return; } - Draw::DataFormat texFmt = FromD3D9Format(dstFmt); - if (plan.depth == 1) { // Regular loop. for (int i = 0; i < levels; i++) { @@ -307,7 +305,7 @@ void TextureCacheDX9::BuildTexture(TexCacheEntry *const entry) { } uint8_t *data = (uint8_t *)rect.pBits; int stride = rect.Pitch; - LoadTextureLevel(*entry, data, stride, *plan.replaced, (i == 0) ? plan.baseLevelSrc : i, plan.scaleFactor, texFmt, false); + LoadTextureLevel(*entry, data, stride, *plan.replaced, (i == 0) ? plan.baseLevelSrc : i, plan.scaleFactor, FromD3D9Format(dstFmt), TexDecodeFlags{}); ((LPDIRECT3DTEXTURE9)texture)->UnlockRect(dstLevel); } } else { @@ -322,7 +320,7 @@ void TextureCacheDX9::BuildTexture(TexCacheEntry *const entry) { uint8_t *data = (uint8_t *)box.pBits; int stride = box.RowPitch; for (int i = 0; i < plan.depth; i++) { - LoadTextureLevel(*entry, data, stride, *plan.replaced, (i == 0) ? plan.baseLevelSrc : i, plan.scaleFactor, texFmt, false); + LoadTextureLevel(*entry, data, stride, *plan.replaced, (i == 0) ? plan.baseLevelSrc : i, plan.scaleFactor, FromD3D9Format(dstFmt), TexDecodeFlags{}); data += box.SlicePitch; } ((LPDIRECT3DVOLUMETEXTURE9)texture)->UnlockBox(0); @@ -456,3 +454,8 @@ bool TextureCacheDX9::GetCurrentTextureDebug(GPUDebugBuffer &buffer, int level) return success; } + +void *TextureCacheDX9::GetNativeTextureView(const TexCacheEntry *entry) { + LPDIRECT3DBASETEXTURE9 tex = DxTex(entry); + return (void *)tex; +} diff --git a/GPU/Directx9/TextureCacheDX9.h b/GPU/Directx9/TextureCacheDX9.h index e8f4d9e58f..58c8e17db6 100644 --- a/GPU/Directx9/TextureCacheDX9.h +++ b/GPU/Directx9/TextureCacheDX9.h @@ -50,6 +50,7 @@ protected: void Unbind() override; void ReleaseTexture(TexCacheEntry *entry, bool delete_them) override; void BindAsClutTexture(Draw::Texture *tex, bool smooth) override; + void *GetNativeTextureView(const TexCacheEntry *entry) override; private: void ApplySamplingParams(const SamplerCacheKey &key) override; @@ -60,7 +61,7 @@ private: void BuildTexture(TexCacheEntry *const entry) override; - LPDIRECT3DBASETEXTURE9 &DxTex(TexCacheEntry *entry) { + LPDIRECT3DBASETEXTURE9 &DxTex(const TexCacheEntry *entry) const { return *(LPDIRECT3DBASETEXTURE9 *)&entry->texturePtr; } diff --git a/GPU/GLES/TextureCacheGLES.cpp b/GPU/GLES/TextureCacheGLES.cpp index 14ebad20be..83309208b8 100644 --- a/GPU/GLES/TextureCacheGLES.cpp +++ b/GPU/GLES/TextureCacheGLES.cpp @@ -266,6 +266,8 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { dstFmt = plan.replaced->Format(plan.baseLevelSrc); } else if (plan.scaleFactor > 1 || plan.saveTexture) { dstFmt = Draw::DataFormat::R8G8B8A8_UNORM; + } else if (plan.decodeToClut8) { + dstFmt = Draw::DataFormat::R8_UNORM; } if (plan.depth == 1) { @@ -313,7 +315,7 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { if (plan.scaleFactor > 1) { bpp = 4; } else { - bpp = dstFmt == Draw::DataFormat::R8G8B8A8_UNORM ? 4 : 2; + bpp = (int)Draw::DataFormatSizeInBytes(dstFmt); } } @@ -325,7 +327,7 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { return; } - LoadTextureLevel(*entry, data, stride, *plan.replaced, srcLevel, plan.scaleFactor, dstFmt, true); + LoadTextureLevel(*entry, data, stride, *plan.replaced, srcLevel, plan.scaleFactor, dstFmt, TexDecodeFlags::REVERSE_COLORS); // NOTE: TextureImage takes ownership of data, so we don't free it afterwards. render_->TextureImage(entry->textureName, i, mipWidth, mipHeight, 1, dstFmt, data, GLRAllocType::ALIGNED); @@ -335,7 +337,7 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { render_->FinalizeTexture(entry->textureName, plan.levelsToLoad, genMips); } else { - int bpp = dstFmt == Draw::DataFormat::R8G8B8A8_UNORM ? 4 : 2; + int bpp = (int)Draw::DataFormatSizeInBytes(dstFmt); int stride = bpp * (plan.w * plan.scaleFactor); int levelStride = stride * (plan.h * plan.scaleFactor); @@ -344,7 +346,7 @@ void TextureCacheGLES::BuildTexture(TexCacheEntry *const entry) { u8 *p = data; for (int i = 0; i < plan.depth; i++) { - LoadTextureLevel(*entry, p, stride, *plan.replaced, i, plan.scaleFactor, dstFmt, true); + LoadTextureLevel(*entry, p, stride, *plan.replaced, i, plan.scaleFactor, dstFmt, TexDecodeFlags::REVERSE_COLORS); p += levelStride; } @@ -443,3 +445,8 @@ void TextureCacheGLES::DeviceRestore(Draw::DrawContext *draw) { render_ = (GLRenderManager *)draw_->GetNativeObject(Draw::NativeObject::RENDER_MANAGER); textureShaderCache_->DeviceRestore(draw); } + +void *TextureCacheGLES::GetNativeTextureView(const TexCacheEntry *entry) { + GLRTexture *tex = entry->textureName; + return (void *)tex; +} diff --git a/GPU/GLES/TextureCacheGLES.h b/GPU/GLES/TextureCacheGLES.h index 1eae540b87..2a4d04574d 100644 --- a/GPU/GLES/TextureCacheGLES.h +++ b/GPU/GLES/TextureCacheGLES.h @@ -66,6 +66,7 @@ protected: void ReleaseTexture(TexCacheEntry *entry, bool delete_them) override; void BindAsClutTexture(Draw::Texture *tex, bool smooth) override; + void *GetNativeTextureView(const TexCacheEntry *entry) override; private: void ApplySamplingParams(const SamplerCacheKey &key) override; diff --git a/GPU/Vulkan/TextureCacheVulkan.cpp b/GPU/Vulkan/TextureCacheVulkan.cpp index 443670e903..56345d4db5 100644 --- a/GPU/Vulkan/TextureCacheVulkan.cpp +++ b/GPU/Vulkan/TextureCacheVulkan.cpp @@ -107,6 +107,15 @@ void main() { )"; +static int VkFormatBytesPerPixel(VkFormat format) { + switch (format) { + case VULKAN_8888_FORMAT: return 4; + case VULKAN_CLUT8_FORMAT: return 1; + default: break; + } + return 2; +} + SamplerCache::~SamplerCache() { DeviceLost(); } @@ -448,6 +457,8 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { if (plan.scaleFactor > 1) { // Whether hardware or software scaling, this is the dest format. dstFmt = VULKAN_8888_FORMAT; + } else if (plan.decodeToClut8) { + dstFmt = VULKAN_CLUT8_FORMAT; } // We don't generate mipmaps for 512x512 textures because they're almost exclusively used for menu backgrounds @@ -479,7 +490,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { case VULKAN_4444_FORMAT: mapping = &VULKAN_4444_SWIZZLE; break; case VULKAN_1555_FORMAT: mapping = &VULKAN_1555_SWIZZLE; break; case VULKAN_565_FORMAT: mapping = &VULKAN_565_SWIZZLE; break; - default: mapping = &VULKAN_8888_SWIZZLE; break; + default: mapping = &VULKAN_8888_SWIZZLE; break; // no swizzle } VkImageLayout imageLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; @@ -562,7 +573,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { int mipHeight; plan.GetMipSize(i, &mipWidth, &mipHeight); - int bpp = actualFmt == VULKAN_8888_FORMAT ? 4 : 2; // output bpp + int bpp = VkFormatBytesPerPixel(actualFmt); int stride = (mipWidth * bpp + 15) & ~15; // output stride int uploadSize = stride * mipHeight; @@ -602,7 +613,7 @@ void TextureCacheVulkan::BuildTexture(TexCacheEntry *const entry) { loadLevel(uploadSize, i, stride, plan.scaleFactor); entry->vkTex->UploadMip(cmdInit, 0, mipWidth, mipHeight, i, texBuf, bufferOffset, stride / bpp); } else if (computeUpload) { - int srcBpp = dstFmt == VULKAN_8888_FORMAT ? 4 : 2; + int srcBpp = VkFormatBytesPerPixel(dstFmt); int srcStride = mipUnscaledWidth * srcBpp; int srcSize = srcStride * mipUnscaledHeight; loadLevel(srcSize, i == 0 ? plan.baseLevelSrc : i, srcStride, 1); @@ -723,12 +734,18 @@ void TextureCacheVulkan::LoadTextureLevel(TexCacheEntry &entry, uint8_t *writePt _assert_msg_(texaddr != 0, "Can't load a texture from address null") int bufw = GetTextureBufw(level, texaddr, tfmt); - int bpp = dstFmt == VULKAN_8888_FORMAT ? 4 : 2; + int bpp = VkFormatBytesPerPixel(dstFmt); u32 *pixelData; int decPitch; - bool expand32 = !gstate_c.Supports(GPU_SUPPORTS_16BIT_FORMATS) || scaleFactor > 1 || dstFmt == VULKAN_8888_FORMAT; + TexDecodeFlags texDecFlags{}; + if (!gstate_c.Supports(GPU_SUPPORTS_16BIT_FORMATS) || scaleFactor > 1 || dstFmt == VULKAN_8888_FORMAT) { + texDecFlags |= TexDecodeFlags::EXPAND32; + } + if (entry.status & TexCacheEntry::STATUS_CLUT_GPU) { + texDecFlags |= TexDecodeFlags::TO_CLUT8; + } if (scaleFactor > 1) { tmpTexBufRearrange_.resize(std::max(bufw, w) * h); @@ -740,7 +757,7 @@ void TextureCacheVulkan::LoadTextureLevel(TexCacheEntry &entry, uint8_t *writePt decPitch = rowPitch; } - CheckAlphaResult alphaResult = DecodeTextureLevel((u8 *)pixelData, decPitch, tfmt, clutformat, texaddr, level, bufw, false, expand32); + CheckAlphaResult alphaResult = DecodeTextureLevel((u8 *)pixelData, decPitch, tfmt, clutformat, texaddr, level, bufw, texDecFlags); entry.SetAlphaStatus(alphaResult, level); if (scaleFactor > 1) { @@ -854,3 +871,8 @@ std::vector TextureCacheVulkan::DebugGetSamplerIDs() const { std::string TextureCacheVulkan::DebugGetSamplerString(std::string id, DebugShaderStringType stringType) { return samplerCache_.DebugGetSamplerString(id, stringType); } + +void *TextureCacheVulkan::GetNativeTextureView(const TexCacheEntry *entry) { + VkImageView view = entry->vkTex->GetImageView(); + return (void *)view; +} diff --git a/GPU/Vulkan/TextureCacheVulkan.h b/GPU/Vulkan/TextureCacheVulkan.h index 64b9da9eab..7dfb38fdc4 100644 --- a/GPU/Vulkan/TextureCacheVulkan.h +++ b/GPU/Vulkan/TextureCacheVulkan.h @@ -97,6 +97,7 @@ protected: void BindAsClutTexture(Draw::Texture *tex, bool smooth) override; void ApplySamplingParams(const SamplerCacheKey &key) override; void BoundFramebufferTexture() override; + void *GetNativeTextureView(const TexCacheEntry *entry) override; private: void LoadTextureLevel(TexCacheEntry &entry, uint8_t *writePtr, int rowPitch, int level, int scaleFactor, VkFormat dstFmt); diff --git a/GPU/Vulkan/VulkanUtil.h b/GPU/Vulkan/VulkanUtil.h index 2753fd79eb..b31fdb75f9 100644 --- a/GPU/Vulkan/VulkanUtil.h +++ b/GPU/Vulkan/VulkanUtil.h @@ -36,6 +36,7 @@ extern const VkComponentMapping VULKAN_8888_SWIZZLE; #define VULKAN_1555_FORMAT VK_FORMAT_A1R5G5B5_UNORM_PACK16 #define VULKAN_565_FORMAT VK_FORMAT_B5G6R5_UNORM_PACK16 // TODO: Does not actually have mandatory support, though R5G6B5 does! See #14602 #define VULKAN_8888_FORMAT VK_FORMAT_R8G8B8A8_UNORM +#define VULKAN_CLUT8_FORMAT VK_FORMAT_R8_UNORM // Manager for compute shaders that upload things (and those have two bindings: a storage buffer to read from and an image to write to). class VulkanComputeShaderManager { diff --git a/GPU/ge_constants.h b/GPU/ge_constants.h index 91ef35b3ec..cdb7cbce58 100644 --- a/GPU/ge_constants.h +++ b/GPU/ge_constants.h @@ -278,20 +278,6 @@ enum GECommand { GE_CMD_NOP_FF = 0xFF, }; -enum GEBufferFormat : uint8_t { - GE_FORMAT_565 = 0, - GE_FORMAT_5551 = 1, - GE_FORMAT_4444 = 2, - GE_FORMAT_8888 = 3, - GE_FORMAT_DEPTH16 = 4, // Virtual format, just used to pass into Depal - GE_FORMAT_INVALID = 0xFF, -}; - -const char *GeBufferFormatToString(GEBufferFormat fmt); -inline bool IsGeBufferFormat16BitColor(GEBufferFormat fmt) { - return (int)fmt < 3; -} - #define GE_VTYPE_TRANSFORM (0<<23) #define GE_VTYPE_THROUGH (1<<23) #define GE_VTYPE_THROUGH_MASK (1<<23) @@ -413,8 +399,10 @@ enum GELightComputation GE_LIGHTCOMP_ONLYPOWDIFFUSE = 2, }; -enum GETextureFormat : uint8_t -{ +// TODO: Consolidate the below three to one enum? The first four are the same in all, +// and we sometimes need to interpret back and forth between them. + +enum GETextureFormat : uint8_t { GE_TFMT_5650 = 0, GE_TFMT_5551 = 1, GE_TFMT_4444 = 2, @@ -428,6 +416,32 @@ enum GETextureFormat : uint8_t GE_TFMT_DXT5 = 10, }; +enum GEBufferFormat : uint8_t { + GE_FORMAT_565 = 0, + GE_FORMAT_5551 = 1, + GE_FORMAT_4444 = 2, + GE_FORMAT_8888 = 3, + GE_FORMAT_DEPTH16 = 4, // Virtual format, just used to pass into Depal + GE_FORMAT_CLUT8 = 5, // Virtual format, for pre-decoded static textures with dynamic CLUT + GE_FORMAT_INVALID = 0xFF, +}; + +enum GEPaletteFormat : uint8_t { + GE_CMODE_16BIT_BGR5650, + GE_CMODE_16BIT_ABGR5551, + GE_CMODE_16BIT_ABGR4444, + GE_CMODE_32BIT_ABGR8888, +}; + +const char *GEPaletteFormatToString(GEPaletteFormat pfmt); +const char *GeTextureFormatToString(GETextureFormat tfmt); +const char *GeTextureFormatToString(GETextureFormat tfmt, GEPaletteFormat pfmt); + +const char *GeBufferFormatToString(GEBufferFormat fmt); +inline bool IsGeBufferFormat16BitColor(GEBufferFormat fmt) { + return (int)fmt < 3; +} + inline bool IsClutFormat(GETextureFormat tfmt) { return tfmt == GE_TFMT_CLUT4 || tfmt == GE_TFMT_CLUT8 || tfmt == GE_TFMT_CLUT16 || tfmt == GE_TFMT_CLUT32; } @@ -445,7 +459,12 @@ inline bool IsTextureFormat16Bit(GETextureFormat tfmt) { } inline int BufferFormatBytesPerPixel(GEBufferFormat format) { - return format == GE_FORMAT_8888 ? 4 : 2; // applies to depth as well. + switch (format) { + case GE_FORMAT_8888: return 4; // applies to depth as well. + case GE_FORMAT_CLUT8: return 1; + default: + return 2; + } } inline bool TextureFormatMatchesBufferFormat(GETextureFormat fmt, GEBufferFormat bfmt) { @@ -613,15 +632,3 @@ inline GEPrimitiveType PatchPrimToPrim(GEPatchPrimType type) { case GE_PATCHPRIM_UNKNOWN: default: return GE_PRIM_POINTS; // Treated as points. } } - -enum GEPaletteFormat -{ - GE_CMODE_16BIT_BGR5650, - GE_CMODE_16BIT_ABGR5551, - GE_CMODE_16BIT_ABGR4444, - GE_CMODE_32BIT_ABGR8888, -}; - -const char *GEPaletteFormatToString(GEPaletteFormat pfmt); -const char *GeTextureFormatToString(GETextureFormat tfmt); -const char *GeTextureFormatToString(GETextureFormat tfmt, GEPaletteFormat pfmt); diff --git a/assets/compat.ini b/assets/compat.ini index 3d2846d377..f79978e9fd 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -1152,6 +1152,17 @@ ULES00262 = true ULUS10064 = true ULKS46087 = true +# Burnout Dominator - lens flare effect (issue #11100) +ULUS10236 = true +ULES00703 = true + +# Need for Speed - Shift (same as Burnout Dominator) +ULUS10462 = true +ULES01275 = true +ULJM05494 = true +NPJH50143 = true +ULJM05738 = true + [DisableFirstFrameReadback] # Wipeout Pure: Temporary workaround for lens flare flicker. See #13344 UCUS98612 = true @@ -1300,5 +1311,31 @@ ULES00262 = true ULUS10064 = true ULKS46087 = true +# Burnout Dominator - lens flare effect (issue #11100) +# Some of the steps don't work at high resolution yet. +ULUS10236 = true +ULES00703 = true + +# Need for Speed - Shift (same as Burnout Dominator) +ULUS10462 = true +ULES01275 = true +ULJM05494 = true +NPJH50143 = true +ULJM05738 = true + [AllowDownloadCLUT] -# Temporary compatibility option, while developing a GPU CLUT-from-framebuffer path. +# Temporary compatibility option, while working on the GPU CLUT-from-framebuffer path. +# Not required for any games now that it works, but might be useful for development. + +[UploadDepthForCLUTTextures] +# Burnout Dominator - lens flare effect (issue #11100) +# We need a preinitialized depth buffer +ULUS10236 = true +ULES00703 = true + +# Need for Speed - Shift (same as Burnout Dominator) +ULUS10462 = true +ULES01275 = true +ULJM05494 = true +NPJH50143 = true +ULJM05738 = true