mirror of
https://github.com/decaf-emu/decaf-emu.git
synced 2026-10-14 01:28:37 +00:00
opengl: Fix failure to reuse color buffers as textures.
Color buffer sizes can only be specified to tile granularity, while texture sizes can be specified to texel granularity. If a game renders to an intermediate framebuffer with a non-tile-aligned effective size, then sets that color buffer as a texture, the texture would be detected as a separate surface due to the size difference, preventing the color buffer's contents from being rendered. (For example, Xenoblade uses intermediate framebuffers of effective size 768x620 and 384x310, but the framebuffer sizes are set to 768x624 and 384x320 respectively.) This change allows surfaces to be resized as long as the guest surface pitch remains unchanged. To deal with differing sizes, texture sampling calls multiply the texture coordinate with a scale factor computed from the texture size specified by the program and the actual OpenGL surface size.
This commit is contained in:
@@ -165,6 +165,19 @@ sampleFunc(State &state,
|
||||
|
||||
insertSelectVector(state.out, src, srcSelX, srcSelY, srcSelZ, srcSelW, samplerElements);
|
||||
|
||||
// TODO: Possible performance bottleneck; this could be improved by
|
||||
// skipping the multiply for textures whose guest and host size are
|
||||
// equal, though that requires including the equality test as part of
|
||||
// the shader key.
|
||||
state.out << " * ";
|
||||
insertSelectVector(state.out,
|
||||
fmt::format("texScale[{}]", resourceID),
|
||||
latte::SQ_SEL_X,
|
||||
latte::SQ_SEL_Y,
|
||||
latte::SQ_SEL_Z,
|
||||
latte::SQ_SEL_W,
|
||||
samplerElements);
|
||||
|
||||
switch (extraArg) {
|
||||
case latte::SQ_SEL_X:
|
||||
state.out << ", ";
|
||||
|
||||
@@ -595,6 +595,8 @@ insertFileHeader(State &state)
|
||||
}
|
||||
|
||||
// Samplers
|
||||
bool hasSamplers = false;
|
||||
|
||||
for (auto id = 0u; id < state.shader->samplerDim.size(); ++id) {
|
||||
auto dim = state.shader->samplerDim[id];
|
||||
auto usage = state.shader->samplerUsage[id];
|
||||
@@ -604,6 +606,8 @@ insertFileHeader(State &state)
|
||||
continue;
|
||||
}
|
||||
|
||||
hasSamplers = true;
|
||||
|
||||
out << "layout (binding = " << id << ") uniform ";
|
||||
|
||||
if (usage == SamplerUsage::Texture) {
|
||||
@@ -671,6 +675,10 @@ insertFileHeader(State &state)
|
||||
out << " sampler_" << id << ";\n";
|
||||
}
|
||||
|
||||
if (hasSamplers) {
|
||||
out << "uniform vec4 texScale[" << latte::MaxTextures << "];\n";
|
||||
}
|
||||
|
||||
if (state.shader->type == Shader::VertexShader) {
|
||||
out
|
||||
<< "out gl_PerVertex {\n"
|
||||
|
||||
@@ -122,7 +122,7 @@ GLDriver::getColorBuffer(latte::CB_COLORN_BASE cb_color_base,
|
||||
decaf_abort(fmt::format("Color buffer with unsupported number type {}", cbNumberType));
|
||||
}
|
||||
|
||||
auto buffer = getSurfaceBuffer(baseAddress, pitch, height, 1, latte::SQ_TEX_DIM_2D, format, numFormat, formatComp, degamma, false);
|
||||
auto buffer = getSurfaceBuffer(baseAddress, pitch, pitch, height, 1, latte::SQ_TEX_DIM_2D, format, numFormat, formatComp, degamma, false);
|
||||
gl::glTextureParameteri(buffer->object, gl::GL_TEXTURE_MAG_FILTER, static_cast<int>(gl::GL_NEAREST));
|
||||
gl::glTextureParameteri(buffer->object, gl::GL_TEXTURE_MIN_FILTER, static_cast<int>(gl::GL_NEAREST));
|
||||
gl::glTextureParameteri(buffer->object, gl::GL_TEXTURE_WRAP_S, static_cast<int>(gl::GL_CLAMP_TO_EDGE));
|
||||
|
||||
@@ -110,7 +110,7 @@ GLDriver::getDepthBuffer(latte::DB_DEPTH_BASE db_depth_base,
|
||||
decaf_abort(fmt::format("Depth buffer with unsupported format {}", dbFormat));
|
||||
}
|
||||
|
||||
auto buffer = getSurfaceBuffer(baseAddress, pitch, height, 1, latte::SQ_TEX_DIM_2D, format, numFormat, formatComp, degamma, true);
|
||||
auto buffer = getSurfaceBuffer(baseAddress, pitch, pitch, height, 1, latte::SQ_TEX_DIM_2D, format, numFormat, formatComp, degamma, true);
|
||||
gl::glTextureParameteri(buffer->object, gl::GL_TEXTURE_MAG_FILTER, static_cast<int>(gl::GL_NEAREST));
|
||||
gl::glTextureParameteri(buffer->object, gl::GL_TEXTURE_MIN_FILTER, static_cast<int>(gl::GL_NEAREST));
|
||||
gl::glTextureParameteri(buffer->object, gl::GL_TEXTURE_WRAP_S, static_cast<int>(gl::GL_CLAMP_TO_EDGE));
|
||||
|
||||
@@ -31,6 +31,7 @@ GLDriver::initGL()
|
||||
applyRegister(static_cast<latte::Register>(i*4));
|
||||
}
|
||||
mActiveShader = nullptr;
|
||||
mTexCoordScale.fill(1.0f);
|
||||
mActiveDepthBuffer = nullptr;
|
||||
memset(&mActiveColorBuffers[0], 0, sizeof(SurfaceBuffer *) * mActiveColorBuffers.size());
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ struct VertexShader : public Resource
|
||||
{
|
||||
gl::GLuint object = 0;
|
||||
gl::GLuint uniformRegisters = 0;
|
||||
gl::GLuint uniformTexScale = 0;
|
||||
std::array<gl::GLuint, latte::MaxAttributes> attribLocations;
|
||||
std::array<uint8_t, 256> outputMap;
|
||||
std::array<bool, 16> usedUniformBlocks;
|
||||
@@ -87,6 +88,7 @@ struct PixelShader : public Resource
|
||||
gl::GLuint object = 0;
|
||||
gl::GLuint uniformRegisters = 0;
|
||||
gl::GLuint uniformAlphaRef = 0;
|
||||
gl::GLuint uniformTexScale = 0;
|
||||
std::array<SamplerType, latte::MaxSamplers> samplerTypes;
|
||||
latte::SX_ALPHA_TEST_CONTROL sx_alpha_test_control;
|
||||
std::array<bool, 16> usedUniformBlocks;
|
||||
@@ -110,12 +112,13 @@ struct Shader
|
||||
struct SurfaceBuffer : Resource
|
||||
{
|
||||
gl::GLuint object = 0;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t depth = 0;
|
||||
SurfaceUseState state = SurfaceUseState::None;
|
||||
bool dirtyAsTexture = true;
|
||||
uint64_t cpuMemHash[2] = { 0 };
|
||||
struct {
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t depth = 0;
|
||||
latte::SQ_TEX_DIM dim;
|
||||
latte::SQ_DATA_FORMAT format;
|
||||
@@ -266,9 +269,16 @@ private:
|
||||
const gsl::span<std::pair<uint32_t, uint32_t>> ®isters);
|
||||
|
||||
SurfaceBuffer *
|
||||
getSurfaceBuffer(ppcaddr_t baseAddress, uint32_t width, uint32_t height, uint32_t depth,
|
||||
latte::SQ_TEX_DIM dim, latte::SQ_DATA_FORMAT format, latte::SQ_NUM_FORMAT numFormat,
|
||||
latte::SQ_FORMAT_COMP formatComp, uint32_t degamma, bool isDepthBuffer);
|
||||
getSurfaceBuffer(ppcaddr_t baseAddress,
|
||||
uint32_t pitch,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
uint32_t depth,
|
||||
latte::SQ_TEX_DIM dim,
|
||||
latte::SQ_DATA_FORMAT format,
|
||||
latte::SQ_NUM_FORMAT numFormat,
|
||||
latte::SQ_FORMAT_COMP formatComp,
|
||||
uint32_t degamma, bool isDepthBuffer);
|
||||
|
||||
SurfaceBuffer *
|
||||
getColorBuffer(latte::CB_COLORN_BASE base,
|
||||
@@ -343,6 +353,8 @@ private:
|
||||
std::array<Sampler, latte::MaxSamplers> mPixelSamplers;
|
||||
std::array<Sampler, latte::MaxSamplers> mGeometrySamplers;
|
||||
|
||||
std::array<float, latte::MaxTextures * 4> mTexCoordScale;
|
||||
|
||||
gl::GLuint mBlitFrameBuffers[2];
|
||||
gl::GLuint mFrameBuffer;
|
||||
gl::GLuint mColorClearFrameBuffer;
|
||||
|
||||
@@ -428,6 +428,7 @@ bool GLDriver::checkActiveShader()
|
||||
|
||||
// Get uniform locations
|
||||
vertexShader.uniformRegisters = gl::glGetUniformLocation(vertexShader.object, "VR");
|
||||
vertexShader.uniformTexScale = gl::glGetUniformLocation(vertexShader.object, "texScale");
|
||||
|
||||
// Get attribute locations
|
||||
vertexShader.attribLocations.fill(0);
|
||||
@@ -485,6 +486,7 @@ bool GLDriver::checkActiveShader()
|
||||
// Get uniform locations
|
||||
pixelShader.uniformRegisters = gl::glGetUniformLocation(pixelShader.object, "PR");
|
||||
pixelShader.uniformAlphaRef = gl::glGetUniformLocation(pixelShader.object, "uAlphaRef");
|
||||
pixelShader.uniformTexScale = gl::glGetUniformLocation(pixelShader.object, "texScale");
|
||||
pixelShader.sx_alpha_test_control = sx_alpha_test_control;
|
||||
}
|
||||
|
||||
|
||||
@@ -281,6 +281,7 @@ static int getStorageFormatBits(gl::GLenum format)
|
||||
|
||||
SurfaceBuffer *
|
||||
GLDriver::getSurfaceBuffer(ppcaddr_t baseAddress,
|
||||
uint32_t pitch,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
uint32_t depth,
|
||||
@@ -299,17 +300,44 @@ GLDriver::getSurfaceBuffer(ppcaddr_t baseAddress,
|
||||
decaf_check(height <= 8192);
|
||||
|
||||
auto surfaceKey = static_cast<uint64_t>(baseAddress) << 32;
|
||||
surfaceKey ^= width ^ height ^ depth ^ dim;
|
||||
surfaceKey ^= format ^ numFormat ^ formatComp ^ degamma;
|
||||
surfaceKey ^= pitch ^ depth<<16 ^ dim<<19;
|
||||
surfaceKey ^= format<<22 ^ numFormat<<28 ^ formatComp<<30 ^ degamma<<31;
|
||||
|
||||
auto bufferIter = mSurfaces.find(surfaceKey);
|
||||
SurfaceBuffer *buffer;
|
||||
gl::GLuint oldSurface = 0;
|
||||
uint32_t oldWidth = 0;
|
||||
uint32_t oldHeight = 0;
|
||||
uint32_t oldDepth = 0;
|
||||
|
||||
if (bufferIter != mSurfaces.end()) {
|
||||
return &bufferIter->second;
|
||||
}
|
||||
buffer = &bufferIter->second;
|
||||
|
||||
auto insertRes = mSurfaces.emplace(surfaceKey, SurfaceBuffer{});
|
||||
auto buffer = &insertRes.first->second;
|
||||
// We need to check the requested size because if the surface is a
|
||||
// strangely-sized (non-tile-aligned) intermediate render buffer,
|
||||
// we'll see different sizes for the surface as a colorbuffer and
|
||||
// as a texture, and we need to make sure both of those go to the
|
||||
// same OpenGL surface.
|
||||
if (width <= buffer->width && height <= buffer->height) {
|
||||
return &bufferIter->second;
|
||||
}
|
||||
|
||||
// The size has increased, so we need to recreate the surface and
|
||||
// copy the pixel data over
|
||||
oldSurface = buffer->object;
|
||||
oldWidth = buffer->width;
|
||||
oldHeight = buffer->height;
|
||||
oldDepth = buffer->depth;
|
||||
|
||||
gLog->info("Expanding surface 0x{:X} from {}x{}x{} to {}x{}x{}",
|
||||
baseAddress, oldWidth, oldHeight, oldDepth, width, height, depth);
|
||||
|
||||
// Recreate the surface with the new size
|
||||
buffer->object = 0;
|
||||
} else {
|
||||
auto insertRes = mSurfaces.emplace(surfaceKey, SurfaceBuffer{});
|
||||
buffer = &insertRes.first->second;
|
||||
}
|
||||
|
||||
auto storageFormat = getStorageFormat(format, numFormat, formatComp, degamma, isDepthBuffer);
|
||||
|
||||
@@ -321,10 +349,12 @@ GLDriver::getSurfaceBuffer(ppcaddr_t baseAddress,
|
||||
// so that we are able to invalidate them as needed.
|
||||
buffer->cpuMemStart = baseAddress;
|
||||
|
||||
// Lets track some other useful information
|
||||
buffer->dbgInfo.width = width;
|
||||
buffer->dbgInfo.height = height;
|
||||
buffer->dbgInfo.depth = depth;
|
||||
// Remember what size we created this surface with (see above)
|
||||
buffer->width = width;
|
||||
buffer->height = height;
|
||||
buffer->depth = depth;
|
||||
|
||||
// Let's track some other useful information
|
||||
buffer->dbgInfo.dim = dim;
|
||||
buffer->dbgInfo.format = format;
|
||||
buffer->dbgInfo.numFormat = numFormat;
|
||||
@@ -333,42 +363,54 @@ GLDriver::getSurfaceBuffer(ppcaddr_t baseAddress,
|
||||
|
||||
auto bitsPerPixel = getStorageFormatBits(storageFormat);
|
||||
uint32_t numPixels;
|
||||
gl::GLenum target;
|
||||
|
||||
if (!buffer->object) {
|
||||
switch (dim) {
|
||||
case latte::SQ_TEX_DIM_1D:
|
||||
gl::glCreateTextures(gl::GL_TEXTURE_1D, 1, &buffer->object);
|
||||
gl::glTextureStorage1D(buffer->object, 1, storageFormat, width);
|
||||
numPixels = width;
|
||||
break;
|
||||
case latte::SQ_TEX_DIM_2D:
|
||||
gl::glCreateTextures(gl::GL_TEXTURE_2D, 1, &buffer->object);
|
||||
gl::glTextureStorage2D(buffer->object, 1, storageFormat, width, height);
|
||||
numPixels = width * height;
|
||||
break;
|
||||
case latte::SQ_TEX_DIM_2D_ARRAY:
|
||||
gl::glCreateTextures(gl::GL_TEXTURE_2D_ARRAY, 1, &buffer->object);
|
||||
gl::glTextureStorage3D(buffer->object, 1, storageFormat, width, height, depth);
|
||||
numPixels = width * height * depth;
|
||||
break;
|
||||
case latte::SQ_TEX_DIM_CUBEMAP:
|
||||
gl::glCreateTextures(gl::GL_TEXTURE_CUBE_MAP, 1, &buffer->object);
|
||||
gl::glTextureStorage2D(buffer->object, 1, storageFormat, width, height);
|
||||
numPixels = width * height * 6;
|
||||
break;
|
||||
case latte::SQ_TEX_DIM_3D:
|
||||
gl::glCreateTextures(gl::GL_TEXTURE_3D, 1, &buffer->object);
|
||||
gl::glTextureStorage3D(buffer->object, 1, storageFormat, width, height, depth);
|
||||
numPixels = width * height * depth;
|
||||
break;
|
||||
case latte::SQ_TEX_DIM_1D_ARRAY:
|
||||
gl::glCreateTextures(gl::GL_TEXTURE_1D_ARRAY, 1, &buffer->object);
|
||||
gl::glTextureStorage2D(buffer->object, 1, storageFormat, width, height);
|
||||
numPixels = width * height;
|
||||
break;
|
||||
default:
|
||||
decaf_abort(fmt::format("Unsupported texture dim: {}", dim));
|
||||
}
|
||||
switch (dim) {
|
||||
case latte::SQ_TEX_DIM_1D:
|
||||
numPixels = width;
|
||||
target = gl::GL_TEXTURE_1D;
|
||||
break;
|
||||
case latte::SQ_TEX_DIM_2D:
|
||||
numPixels = width * height;
|
||||
target = gl::GL_TEXTURE_2D;
|
||||
break;
|
||||
case latte::SQ_TEX_DIM_2D_ARRAY:
|
||||
numPixels = width * height * depth;
|
||||
target = gl::GL_TEXTURE_2D_ARRAY;
|
||||
break;
|
||||
case latte::SQ_TEX_DIM_CUBEMAP:
|
||||
numPixels = width * height * 6;
|
||||
target = gl::GL_TEXTURE_CUBE_MAP;
|
||||
break;
|
||||
case latte::SQ_TEX_DIM_3D:
|
||||
numPixels = width * height * depth;
|
||||
target = gl::GL_TEXTURE_3D;
|
||||
break;
|
||||
case latte::SQ_TEX_DIM_1D_ARRAY:
|
||||
numPixels = width * height;
|
||||
target = gl::GL_TEXTURE_1D_ARRAY;
|
||||
break;
|
||||
default:
|
||||
decaf_abort(fmt::format("Unsupported texture dim: {}", dim));
|
||||
}
|
||||
|
||||
gl::glCreateTextures(target, 1, &buffer->object);
|
||||
|
||||
switch (target) {
|
||||
case gl::GL_TEXTURE_1D:
|
||||
gl::glTextureStorage1D(buffer->object, 1, storageFormat, width);
|
||||
break;
|
||||
case gl::GL_TEXTURE_1D_ARRAY:
|
||||
case gl::GL_TEXTURE_2D:
|
||||
case gl::GL_TEXTURE_CUBE_MAP:
|
||||
gl::glTextureStorage2D(buffer->object, 1, storageFormat, width, height);
|
||||
break;
|
||||
case gl::GL_TEXTURE_2D_ARRAY:
|
||||
case gl::GL_TEXTURE_3D:
|
||||
gl::glTextureStorage3D(buffer->object, 1, storageFormat, width, height, depth);
|
||||
break;
|
||||
default:
|
||||
decaf_abort(fmt::format("impossible target {}", static_cast<unsigned>(target)));
|
||||
}
|
||||
|
||||
// numBits could theoretically be >= 2^32, so uint64_t to avoid overflow
|
||||
@@ -382,6 +424,15 @@ GLDriver::getSurfaceBuffer(ppcaddr_t baseAddress,
|
||||
decaf_check(numBits / 8 < UINT64_C(1) << 32);
|
||||
buffer->cpuMemEnd = baseAddress + static_cast<uint32_t>(numBits / 8);
|
||||
|
||||
// If we're resizing an existing surface, copy the pixel data and destroy
|
||||
// the old OpenGL surface
|
||||
if (oldSurface) {
|
||||
glCopyImageSubData(oldSurface, target, 0, 0, 0, 0,
|
||||
buffer->object, target, 0, 0, 0, 0,
|
||||
width, height, depth);
|
||||
gl::glDeleteTextures(1, &oldSurface);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
@@ -312,7 +312,7 @@ bool GLDriver::checkActiveTextures()
|
||||
auto dim = sq_tex_resource_word0.DIM();
|
||||
auto isDepthBuffer = !!sq_tex_resource_word0.TILE_TYPE();
|
||||
|
||||
auto buffer = getSurfaceBuffer(baseAddress, width, height, depth, dim, format, numFormat, formatComp, degamma, isDepthBuffer);
|
||||
auto buffer = getSurfaceBuffer(baseAddress, pitch, width, height, depth, dim, format, numFormat, formatComp, degamma, isDepthBuffer);
|
||||
|
||||
if (buffer->dirtyAsTexture) {
|
||||
auto swizzle = sq_tex_resource_word2.SWIZZLE() << 8;
|
||||
@@ -493,6 +493,26 @@ bool GLDriver::checkActiveTextures()
|
||||
|
||||
gl::glTextureParameteriv(buffer->object, gl::GL_TEXTURE_SWIZZLE_RGBA, textureSwizzle);
|
||||
gl::glBindTextureUnit(i, buffer->object);
|
||||
|
||||
// Store texture coordinate scale factors for shaders
|
||||
mTexCoordScale[i * 4 + 0] = static_cast<float>(width) / static_cast<float>(buffer->width);
|
||||
mTexCoordScale[i * 4 + 1] = static_cast<float>(height) / static_cast<float>(buffer->height);
|
||||
mTexCoordScale[i * 4 + 2] = static_cast<float>(depth) / static_cast<float>(buffer->depth);
|
||||
}
|
||||
|
||||
// Send texture scale array to shaders
|
||||
if (mActiveShader->vertex->uniformTexScale != -1) {
|
||||
gl::glProgramUniform4fv(mActiveShader->vertex->object,
|
||||
mActiveShader->vertex->uniformTexScale,
|
||||
latte::MaxTextures,
|
||||
&mTexCoordScale[0]);
|
||||
}
|
||||
|
||||
if (mActiveShader->pixel && mActiveShader->pixel->uniformTexScale != -1) {
|
||||
gl::glProgramUniform4fv(mActiveShader->pixel->object,
|
||||
mActiveShader->pixel->uniformTexScale,
|
||||
latte::MaxTextures,
|
||||
&mTexCoordScale[0]);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user