Merge branch 'hrydgard:master' into compat_openxr_gta

This commit is contained in:
Luboš Vonásek authored and GitHub committed 2022-09-16 10:20:11 +02:00
commit 861af63be9
40 files changed
+723 -165

No files matched your search

+8
View File
@@ -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));
+19
View File
@@ -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;
}
+2
View File
@@ -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,
+15 -1
View File
@@ -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;
+1 -1
View File
@@ -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);
}
+2 -2
View File
@@ -385,7 +385,7 @@ void GLQueueRunner::RunInitSteps(const std::vector<GLRInitStep> &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);
+26 -11
View File
@@ -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_);
+1
View File
@@ -1041,6 +1041,7 @@ private:
bool nextFrame = false;
bool firstFrame = true;
bool vrRenderStarted = false;
GLDeleter deleter_;
bool skipGLCalls_ = false;
+10 -1
View File
@@ -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:
@@ -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);
+14
View File
@@ -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)) {
+3
View File
@@ -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;
+8
View File
@@ -557,6 +557,7 @@ typedef std::function<bool(uint8_t *data, const uint8_t *initData, uint32_t w, u
struct TextureDesc {
TextureType type;
DataFormat format;
int width;
int height;
int depth;
@@ -674,6 +675,13 @@ public:
virtual void BindVertexBuffers(int start, int count, Buffer **buffers, const int *offsets) = 0;
virtual void BindIndexBuffer(Buffer *indexBuffer, int offset) = 0;
// Sometimes it's necessary to bind a texture not created by thin3d, and use with a thin3d pipeline.
// Not pretty, and one way in the future could be to create all textures through thin3d.
// Data types:
// * Vulkan: VkImageView
// * D3D11: ID3D11ShaderResourceView*
virtual void BindNativeTexture(int sampler, void *nativeTexture) = 0;
// Only supports a single dynamic uniform buffer, for maximum compatibility with the old APIs and ease of emulation.
// More modern methods will be added later.
virtual void UpdateDynamicUniformBuffer(const void *ub, size_t size) = 0;
+1 -1
View File
@@ -628,7 +628,7 @@ void RiscVEmitter::FlushIcache() {
void RiscVEmitter::FlushIcacheSection(const u8 *start, const u8 *end) {
#if PPSSPP_ARCH(RISCV64)
__builtin___clear_cache(start, end);
__builtin___clear_cache((void *)start, (void *)end);
#endif
}
+2 -2
View File
@@ -284,7 +284,7 @@ void BindVRFramebuffer() {
VR_BindFramebuffer(VR_GetEngine());
}
bool PreVRRender() {
bool StartVRRender() {
if (!VR_GetConfig(VR_CONFIG_VIEWPORT_VALID)) {
VR_InitRenderer(VR_GetEngine(), IsMultiviewSupported());
VR_SetConfig(VR_CONFIG_VIEWPORT_VALID, true);
@@ -312,7 +312,7 @@ bool PreVRRender() {
return false;
}
void PostVRRender() {
void FinishVRRender() {
VR_FinishFrame(VR_GetEngine());
}
+4 -4
View File
@@ -35,8 +35,8 @@ void SetVRCompat(VRCompatFlag flag, long value);
// VR rendering integration
void BindVRFramebuffer();
bool PreVRRender();
void PostVRRender();
bool StartVRRender();
void FinishVRRender();
void PreVRFrameRender(int fboIndex);
void PostVRFrameRender();
int GetVRFBOIndex();
@@ -64,8 +64,8 @@ inline void SetVRCompat(VRCompatFlag flag, long value) {}
// VR rendering integration
inline void BindVRFramebuffer() {}
inline bool PreVRRender() { return false; }
inline void PostVRRender() {}
inline bool StartVRRender() { return false; }
inline void FinishVRRender() {}
inline void PreVRFrameRender(int fboIndex) {}
inline void PostVRFrameRender() {}
inline int GetVRFBOIndex() { return 0; }
+1
View File
@@ -109,6 +109,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) {
CheckSetting(iniFile, gameID, "SplitFramebufferMargin", &flags_.SplitFramebufferMargin);
CheckSetting(iniFile, gameID, "ForceLowerResolutionForEffectsOn", &flags_.ForceLowerResolutionForEffectsOn);
CheckSetting(iniFile, gameID, "AllowDownloadCLUT", &flags_.AllowDownloadCLUT);
CheckSetting(iniFile, gameID, "UploadDepthForCLUTTextures", &flags_.UploadDepthForCLUTTextures);
}
void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, bool *flag) {
+1
View File
@@ -89,6 +89,7 @@ struct CompatFlags {
bool SplitFramebufferMargin;
bool ForceLowerResolutionForEffectsOn;
bool AllowDownloadCLUT;
bool UploadDepthForCLUTTextures;
};
struct VRCompat {
+13
View File
@@ -84,6 +84,9 @@ void GenerateDepalShader300(ShaderWriter &writer, const DepalConfig &config) {
int shiftedMask = mask << shift;
switch (config.bufferFormat) {
case GE_FORMAT_CLUT8:
writer.C(" int index = int(color.r * 255.99);\n");
break;
case GE_FORMAT_8888:
if (shiftedMask & 0xFF) writer.C(" int r = int(color.r * 255.99);\n"); else writer.C(" int r = 0;\n");
if (shiftedMask & 0xFF00) writer.C(" int g = int(color.g * 255.99);\n"); else writer.C(" int g = 0;\n");
@@ -168,6 +171,16 @@ void GenerateDepalShaderFloat(ShaderWriter &writer, const DepalConfig &config) {
// pixelformat is the format of the texture we are sampling.
bool formatOK = true;
switch (config.bufferFormat) {
case GE_FORMAT_CLUT8:
if (shift == 0 && mask == 0xFF) {
// Easy peasy.
sprintf(lookupMethod, "index.r");
formatOK = true;
} else {
// Deal with this if we find it.
formatOK = false;
}
break;
case GE_FORMAT_8888:
if ((mask & (mask + 1)) == 0) {
// If the value has all bits contiguous (bitmask check above), we can mod by it + 1.
+21 -2
View File
@@ -734,8 +734,27 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
WRITE(p, " }\n");
break;
case ShaderDepalMode::CLUT8_8888:
// Not yet implemented.
WRITE(p, " vec4 t = vec4(0.0, 0.0, 0.0, 0.0);\n");
if (doTextureProjection) {
// We don't use textureProj because we need better control and it's probably not much of a savings anyway.
// However it is good for precision on older hardware like PowerVR.
p.F(" vec2 uv = %s.xy/%s.z;\n vec2 uv_round;\n", texcoord, texcoord);
} else {
p.F(" vec2 uv = %s.xy;\n vec2 uv_round;\n", texcoord);
}
p.C(" vec2 tsize = vec2(textureSize(tex, 0).xy);\n");
p.C(" uv_round = floor(uv * tsize);\n");
p.C(" int component = int(uv_round.x) & 3;\n");
p.C(" uv_round.x *= 0.25;\n");
p.C(" uv_round /= tsize;\n");
p.C(" vec4 t = ").SampleTexture2D("tex", "uv_round").C(";\n");
p.C(" int index;\n");
p.C(" switch (component) {\n");
p.C(" case 0: index = int(t.x * 254.99); break;\n"); // TODO: Not sure why 254.99 instead of 255.99, but it's currently needed.
p.C(" case 1: index = int(t.y * 254.99); break;\n");
p.C(" case 2: index = int(t.z * 254.99); break;\n");
p.C(" case 3: index = int(t.w * 254.99); break;\n");
p.C(" }\n");
p.C(" t = ").LoadTexture2D("pal", "ivec2(index, 0)", 0).C(";\n");
break;
}
+86 -24
View File
@@ -475,9 +475,9 @@ VirtualFramebuffer *FramebufferManagerCommon::DoSetRenderFrameBuffer(Framebuffer
vfb->fb_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<int>(dstX * dstXFactor), dstY, srcBase, dstRect.vfb->fb_format, static_cast<int>(srcStride * dstXFactor), static_cast<int>(dstRect.w_bytes / bpp * dstXFactor), dstRect.h);
DrawPixels(dstRect.vfb, static_cast<int>(dstX * dstXFactor), dstY, srcBase, dstRect.vfb->fb_format, static_cast<int>(srcStride * dstXFactor), static_cast<int>(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) {
+12 -8
View File
@@ -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);
+25 -12
View File
@@ -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;
}
}
}
+1
View File
@@ -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;
+1 -1
View File
@@ -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;
+5 -5
View File
@@ -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 =
+276 -39
View File
@@ -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<VirtualFramebuffer *> &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) {
+24 -2
View File
@@ -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 <typename T>
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;
+1 -1
View File
@@ -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;
+10 -1
View File
@@ -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;
}
+3 -2
View File
@@ -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;
}
+7 -4
View File
@@ -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;
}
+2 -1
View File
@@ -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;
}
+11 -4
View File
@@ -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;
}
+1
View File
@@ -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;
+28 -6
View File
@@ -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<std::string> 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;
}
+1
View File
@@ -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);
+1
View File
@@ -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 {
+36 -29
View File
@@ -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);
+38 -1
View File
@@ -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