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:
Andrew Church
2016-08-12 02:11:46 +09:00
parent 34859afb42
commit 9674136d07
9 changed files with 160 additions and 53 deletions

View File

@@ -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 << ", ";

View File

@@ -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"

View File

@@ -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));

View File

@@ -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));

View File

@@ -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());

View File

@@ -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>> &registers);
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;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;