Custom textures rewrite (#6452)

* common: Add thread pool from yuzu

* Is really useful for asynchronous operations like shader compilation and custom textures, will be used in following PRs

* core: Improve ImageInterface

* Provide a default implementation so frontends don't have to duplicate code registering the lodepng version

* Add a dds version too which we will use in the next commit

* rasterizer_cache: Rewrite custom textures

* There's just too much to talk about here, look at the PR description for more details

* rasterizer_cache: Implement basic pack configuration file

* custom_tex_manager: Flip dumped textures

* custom_tex_manager: Optimize custom texture hashing

* If no convertions are needed then we can hash the decoded data directly removing the needed for duplicate decode

* custom_tex_manager: Implement asynchronous texture loading

* The file loading and decoding is offloaded into worker threads, while the upload itself still occurs in the main thread to avoid having to manage shared contexts

* Address review comments

* custom_tex_manager: Introduce custom material support

* video_core: Move custom textures to separate directory

* Also split the files to make the code cleaner

* gl_texture_runtime: Generate mipmaps for material

* custom_tex_manager: Prevent memory overflow when preloading

* externals: Add dds-ktx as submodule

* string_util: Return vector from SplitString

* No code benefits from passing it as an argument

* custom_textures: Use json config file

* gl_rasterizer: Only bind material for unit 0

* Address review comments
This commit is contained in:
GPUCode 2023-04-27 07:38:28 +03:00 committed by GitHub
parent d16dce6d99
commit 06f3c90cfb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
87 changed files with 2154 additions and 544 deletions

View file

@ -13,7 +13,6 @@
#include "video_core/host_shaders/texture_filtering/nearest_neighbor_frag.h"
#include "video_core/host_shaders/texture_filtering/refine_frag.h"
#include "video_core/host_shaders/texture_filtering/scale_force_frag.h"
#include "video_core/host_shaders/texture_filtering/tex_coord_vert.h"
#include "video_core/host_shaders/texture_filtering/x_gradient_frag.h"
#include "video_core/host_shaders/texture_filtering/xbrz_freescale_frag.h"
#include "video_core/host_shaders/texture_filtering/y_gradient_frag.h"
@ -81,7 +80,7 @@ bool BlitHelper::Filter(Surface& surface, const VideoCore::TextureBlit& blit) {
}
const OpenGLState prev_state = OpenGLState::GetCurState();
state.texture_units[0].texture_2d = surface.Handle(false);
state.texture_units[0].texture_2d = surface.Handle(0);
const auto filter{Settings::values.texture_filter.GetValue()};
switch (filter) {
@ -135,7 +134,7 @@ void BlitHelper::FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& b
auto LUMAD = setup_temp_tex(GL_R16F, GL_RED, temp_rect.GetWidth(), temp_rect.GetHeight());
// Copy to SRC
glCopyImageSubData(surface.Handle(false), GL_TEXTURE_2D, 0, blit.src_rect.left,
glCopyImageSubData(surface.Handle(0), GL_TEXTURE_2D, 0, blit.src_rect.left,
blit.src_rect.bottom, 0, SRC.tex.handle, GL_TEXTURE_2D, 0, 0, 0, 0,
src_width, src_height, 1);
@ -161,47 +160,42 @@ void BlitHelper::FilterAnime4K(Surface& surface, const VideoCore::TextureBlit& b
}
void BlitHelper::FilterBicubic(Surface& surface, const VideoCore::TextureBlit& blit) {
SetParams(bicubic_program, surface.width, surface.height, blit.src_rect);
SetParams(bicubic_program, surface.Extent(), blit.src_rect);
Draw(bicubic_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect);
}
void BlitHelper::FilterNearest(Surface& surface, const VideoCore::TextureBlit& blit) {
state.texture_units[2].texture_2d = surface.Handle(false);
SetParams(nearest_program, surface.width, surface.height, blit.src_rect);
state.texture_units[2].texture_2d = surface.Handle(0);
SetParams(nearest_program, surface.Extent(), blit.src_rect);
Draw(nearest_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect);
}
void BlitHelper::FilterScaleForce(Surface& surface, const VideoCore::TextureBlit& blit) {
SetParams(scale_force_program, surface.width, surface.height, blit.src_rect);
SetParams(scale_force_program, surface.Extent(), blit.src_rect);
Draw(scale_force_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect);
}
void BlitHelper::FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit) {
glProgramUniform1f(xbrz_program.handle, 2, static_cast<GLfloat>(surface.res_scale));
SetParams(xbrz_program, surface.width, surface.height, blit.src_rect);
SetParams(xbrz_program, surface.Extent(), blit.src_rect);
Draw(xbrz_program, surface.Handle(), filter_fbo.handle, blit.dst_level, blit.dst_rect);
}
void BlitHelper::SetParams(OGLProgram& program, u32 src_width, u32 src_height,
void BlitHelper::SetParams(OGLProgram& program, const VideoCore::Extent& src_extent,
Common::Rectangle<u32> src_rect) {
glProgramUniform2f(
program.handle, 0,
static_cast<float>(src_rect.right - src_rect.left) / static_cast<float>(src_width),
static_cast<float>(src_rect.top - src_rect.bottom) / static_cast<float>(src_height));
static_cast<float>(src_rect.right - src_rect.left) / static_cast<float>(src_extent.width),
static_cast<float>(src_rect.top - src_rect.bottom) / static_cast<float>(src_extent.height));
glProgramUniform2f(program.handle, 1,
static_cast<float>(src_rect.left) / static_cast<float>(src_width),
static_cast<float>(src_rect.bottom) / static_cast<float>(src_height));
static_cast<float>(src_rect.left) / static_cast<float>(src_extent.width),
static_cast<float>(src_rect.bottom) / static_cast<float>(src_extent.height));
}
void BlitHelper::Draw(OGLProgram& program, GLuint dst_tex, GLuint dst_fbo, u32 dst_level,
Common::Rectangle<u32> dst_rect) {
state.draw.draw_framebuffer = dst_fbo;
state.draw.shader_program = program.handle;
state.scissor.enabled = true;
state.scissor.x = dst_rect.left;
state.scissor.y = dst_rect.bottom;
state.scissor.width = dst_rect.GetWidth();
state.scissor.height = dst_rect.GetHeight();
state.viewport.x = dst_rect.left;
state.viewport.y = dst_rect.bottom;
state.viewport.width = dst_rect.GetWidth();
@ -212,7 +206,6 @@ void BlitHelper::Draw(OGLProgram& program, GLuint dst_tex, GLuint dst_fbo, u32 d
dst_level);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

View file

@ -9,8 +9,9 @@
#include "video_core/renderer_opengl/gl_state.h"
namespace VideoCore {
struct Extent;
struct TextureBlit;
}
} // namespace VideoCore
namespace OpenGL {
@ -35,7 +36,7 @@ private:
void FilterXbrz(Surface& surface, const VideoCore::TextureBlit& blit);
void SetParams(OGLProgram& program, u32 src_width, u32 src_height,
void SetParams(OGLProgram& program, const VideoCore::Extent& src_extent,
Common::Rectangle<u32> src_rect);
void Draw(OGLProgram& program, GLuint dst_tex, GLuint dst_fbo, u32 dst_level,

View file

@ -6,6 +6,7 @@
#include "common/assert.h"
#include "common/settings.h"
#include "core/telemetry_session.h"
#include "video_core/custom_textures/custom_format.h"
#include "video_core/renderer_opengl/gl_driver.h"
namespace OpenGL {
@ -103,6 +104,25 @@ bool Driver::HasDebugTool() {
return false;
}
bool Driver::IsCustomFormatSupported(VideoCore::CustomPixelFormat format) const {
switch (format) {
case VideoCore::CustomPixelFormat::RGBA8:
return true;
case VideoCore::CustomPixelFormat::BC1:
case VideoCore::CustomPixelFormat::BC3:
case VideoCore::CustomPixelFormat::BC5:
return ext_texture_compression_s3tc;
case VideoCore::CustomPixelFormat::BC7:
return arb_texture_compression_bptc;
case VideoCore::CustomPixelFormat::ASTC4:
case VideoCore::CustomPixelFormat::ASTC6:
case VideoCore::CustomPixelFormat::ASTC8:
return is_gles;
default:
return false;
}
}
void Driver::ReportDriverInfo() {
// Report the context version and the vendor string
gl_version = std::string_view{reinterpret_cast<const char*>(glGetString(GL_VERSION))};
@ -145,7 +165,9 @@ void Driver::CheckExtensionSupport() {
arb_buffer_storage = GLAD_GL_ARB_buffer_storage;
arb_clear_texture = GLAD_GL_ARB_clear_texture;
arb_get_texture_sub_image = GLAD_GL_ARB_get_texture_sub_image;
arb_texture_compression_bptc = GLAD_GL_ARB_texture_compression_bptc;
ext_clip_cull_distance = GLAD_GL_EXT_clip_cull_distance;
ext_texture_compression_s3tc = GLAD_GL_EXT_texture_compression_s3tc;
is_suitable = GLAD_GL_VERSION_4_3 || GLAD_GL_ES_VERSION_3_1;
}

View file

@ -11,6 +11,10 @@ namespace Core {
class TelemetrySession;
}
namespace VideoCore {
enum class CustomPixelFormat : u32;
}
namespace OpenGL {
enum class Vendor {
@ -51,6 +55,9 @@ public:
/// Returns true if any debug tool is attached
bool HasDebugTool();
/// Returns true if the driver supports the provided custom format
bool IsCustomFormatSupported(VideoCore::CustomPixelFormat format) const;
/// Returns the vendor of the currently selected physical device
Vendor GetVendor() const {
return vendor;
@ -114,6 +121,8 @@ private:
bool arb_clear_texture{};
bool arb_get_texture_sub_image{};
bool ext_clip_cull_distance{};
bool ext_texture_compression_s3tc{};
bool arb_texture_compression_bptc{};
std::string_view gl_version{};
std::string_view gpu_vendor{};

View file

@ -73,11 +73,13 @@ GLenum MakeAttributeType(Pica::PipelineRegs::VertexAttributeFormat format) {
} // Anonymous namespace
RasterizerOpenGL::RasterizerOpenGL(Memory::MemorySystem& memory, VideoCore::RendererBase& renderer,
Driver& driver_)
RasterizerOpenGL::RasterizerOpenGL(Memory::MemorySystem& memory,
VideoCore::CustomTexManager& custom_tex_manager,
VideoCore::RendererBase& renderer, Driver& driver_)
: VideoCore::RasterizerAccelerated{memory}, driver{driver_}, runtime{driver, renderer},
res_cache{memory, runtime, regs, renderer}, texture_buffer_size{TextureBufferSize()},
vertex_buffer{driver, GL_ARRAY_BUFFER, VERTEX_BUFFER_SIZE},
res_cache{memory, custom_tex_manager, runtime, regs, renderer},
texture_buffer_size{TextureBufferSize()}, vertex_buffer{driver, GL_ARRAY_BUFFER,
VERTEX_BUFFER_SIZE},
uniform_buffer{driver, GL_UNIFORM_BUFFER, UNIFORM_BUFFER_SIZE},
index_buffer{driver, GL_ELEMENT_ARRAY_BUFFER, INDEX_BUFFER_SIZE},
texture_buffer{driver, GL_TEXTURE_BUFFER, texture_buffer_size}, texture_lf_buffer{
@ -183,6 +185,10 @@ RasterizerOpenGL::RasterizerOpenGL(Memory::MemorySystem& memory, VideoCore::Rend
RasterizerOpenGL::~RasterizerOpenGL() = default;
void RasterizerOpenGL::TickFrame() {
res_cache.TickFrame();
}
void RasterizerOpenGL::LoadDiskResources(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) {
shader_program_manager->LoadDiskCache(stop_loading, callback);
@ -420,7 +426,6 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
state.scissor.y = draw_rect.bottom;
state.scissor.width = draw_rect.GetWidth();
state.scissor.height = draw_rect.GetHeight();
state.Apply();
const u32 res_scale = framebuffer.ResolutionScale();
if (uniform_block_data.data.framebuffer_scale != res_scale) {
@ -444,10 +449,11 @@ bool RasterizerOpenGL::Draw(bool accelerate, bool is_indexed) {
// Sync and bind the texture surfaces
SyncTextureUnits(framebuffer);
state.Apply();
// Sync and bind the shader
if (shader_dirty) {
SetShader();
shader_program_manager->UseFragmentShader(regs, use_custom_normal);
shader_dirty = false;
}
@ -546,6 +552,7 @@ void RasterizerOpenGL::SyncTextureUnits(const Framebuffer& framebuffer) {
// on the male character's face, which in the OpenGL default appear black.
state.texture_units[texture_index].texture_2d = default_texture;
} else if (!IsFeedbackLoop(texture_index, framebuffer, *surface)) {
BindMaterial(texture_index, *surface);
state.texture_units[texture_index].texture_2d = surface->Handle();
}
}
@ -589,6 +596,33 @@ void RasterizerOpenGL::BindTextureCube(const Pica::TexturingRegs::FullTextureCon
state.texture_units[0].texture_2d = 0;
}
void RasterizerOpenGL::BindMaterial(u32 texture_index, Surface& surface) {
if (!surface.IsCustom() || texture_index != 0) {
return;
}
const auto bind_texture = [&](const TextureUnits::TextureUnit& unit, GLuint texture,
GLuint sampler) {
glActiveTexture(unit.Enum());
glBindTexture(GL_TEXTURE_2D, texture);
glBindSampler(unit.id, sampler);
};
const GLuint sampler = texture_samplers[texture_index].sampler.handle;
if (surface.HasNormalMap()) {
if (regs.lighting.disable) {
LOG_WARNING(Render_OpenGL, "Custom normal map used but scene has no light enabled");
}
bind_texture(TextureUnits::TextureNormalMap, surface.Handle(2), sampler);
use_custom_normal = true;
} else {
if (use_custom_normal) {
bind_texture(TextureUnits::TextureNormalMap, 0, 0);
}
use_custom_normal = false;
}
}
bool RasterizerOpenGL::IsFeedbackLoop(u32 texture_index, const Framebuffer& framebuffer,
Surface& surface) {
const GLuint color_attachment = framebuffer.Attachment(SurfaceType::Color);
@ -622,7 +656,6 @@ void RasterizerOpenGL::UnbindSpecial() {
state.image_shadow_texture_pz = 0;
state.image_shadow_texture_nz = 0;
state.image_shadow_buffer = 0;
state.Apply();
}
void RasterizerOpenGL::NotifyFixedFunctionPicaRegisterChanged(u32 id) {
@ -823,10 +856,6 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(
}
}
void RasterizerOpenGL::SetShader() {
shader_program_manager->UseFragmentShader(Pica::g_state.regs);
}
void RasterizerOpenGL::SyncClipEnabled() {
state.clip_distance[1] = Pica::g_state.regs.rasterizer.clip_enable != 0;
}

View file

@ -18,6 +18,10 @@ namespace VideoCore {
class RendererBase;
}
namespace VideoCore {
class CustomTexManager;
}
namespace OpenGL {
class Driver;
@ -25,10 +29,12 @@ class ShaderProgramManager;
class RasterizerOpenGL : public VideoCore::RasterizerAccelerated {
public:
explicit RasterizerOpenGL(Memory::MemorySystem& memory, VideoCore::RendererBase& renderer,
Driver& driver);
explicit RasterizerOpenGL(Memory::MemorySystem& memory,
VideoCore::CustomTexManager& custom_tex_manager,
VideoCore::RendererBase& renderer, Driver& driver);
~RasterizerOpenGL() override;
void TickFrame();
void LoadDiskResources(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) override;
@ -74,9 +80,6 @@ private:
/// Syncs the clip enabled status to match the PICA register
void SyncClipEnabled();
/// Sets the OpenGL shader in accordance with the current PICA register state
void SetShader();
/// Syncs the cull mode to match the PICA register
void SyncCullMode();
@ -126,6 +129,9 @@ private:
/// Unbinds all special texture unit 0 texture configurations
void UnbindSpecial();
/// Binds the custom material referenced by surface if it exists.
void BindMaterial(u32 texture_index, Surface& surface);
/// Upload the uniform blocks to the uniform buffer object
void UploadUniforms(bool accelerate_draw);
@ -174,6 +180,7 @@ private:
OGLTexture texture_buffer_lut_lf;
OGLTexture texture_buffer_lut_rg;
OGLTexture texture_buffer_lut_rgba;
bool use_custom_normal{};
};
} // namespace OpenGL

View file

@ -59,7 +59,7 @@ out gl_PerVertex {
return out;
}
PicaFSConfig PicaFSConfig::BuildFromRegs(const Pica::Regs& regs) {
PicaFSConfig PicaFSConfig::BuildFromRegs(const Pica::Regs& regs, bool use_normal) {
PicaFSConfig res{};
auto& state = res.state;
@ -204,6 +204,8 @@ PicaFSConfig PicaFSConfig::BuildFromRegs(const Pica::Regs& regs) {
state.shadow_texture_orthographic = regs.texturing.shadow.orthographic != 0;
state.use_custom_normal_map = use_normal;
return res;
}
@ -297,6 +299,8 @@ static std::string SampleTexture(const PicaFSConfig& config, unsigned texture_un
LOG_DEBUG(Render_OpenGL, "Using Texture3 without enabling it");
return "vec4(0.0)";
}
case 4:
return "texture(tex_normal, texcoord0)";
default:
UNREACHABLE();
return "";
@ -642,7 +646,12 @@ static void WriteLighting(std::string& out, const PicaFSConfig& config) {
const auto Perturbation = [&] {
return fmt::format("2.0 * ({}).rgb - 1.0", SampleTexture(config, lighting.bump_selector));
};
if (lighting.bump_mode == LightingRegs::LightingBumpMode::NormalMap) {
if (config.state.use_custom_normal_map) {
const std::string normal_texel =
fmt::format("2.0 * ({}).rgb - 1.0", SampleTexture(config, 4));
out += fmt::format("vec3 surface_normal = {};\n", normal_texel);
out += "vec3 surface_tangent = vec3(1.0, 0.0, 0.0);\n";
} else if (lighting.bump_mode == LightingRegs::LightingBumpMode::NormalMap) {
// Bump mapping is enabled using a normal map
out += fmt::format("vec3 surface_normal = {};\n", Perturbation());
@ -1207,6 +1216,7 @@ out vec4 color;
uniform sampler2D tex0;
uniform sampler2D tex1;
uniform sampler2D tex2;
uniform sampler2D tex_normal; //< Used for custom normal maps
uniform samplerCube tex_cube;
uniform samplerBuffer texture_buffer_lut_lf;
uniform samplerBuffer texture_buffer_lut_rg;

View file

@ -117,6 +117,7 @@ struct PicaFSConfigState {
bool shadow_rendering;
bool shadow_texture_orthographic;
bool use_custom_normal_map;
};
/**
@ -130,7 +131,7 @@ struct PicaFSConfigState {
struct PicaFSConfig : Common::HashableStruct<PicaFSConfigState> {
/// Construct a PicaFSConfig with the given Pica register configuration.
static PicaFSConfig BuildFromRegs(const Pica::Regs& regs);
static PicaFSConfig BuildFromRegs(const Pica::Regs& regs, bool use_normal = false);
bool TevStageUpdatesCombinerBufferColor(unsigned stage_index) const {
return (stage_index < 4) && (state.combiner_buffer_input & (1 << stage_index));

View file

@ -133,6 +133,7 @@ static void SetShaderSamplerBindings(GLuint shader) {
SetShaderSamplerBinding(shader, "tex1", TextureUnits::PicaTexture(1));
SetShaderSamplerBinding(shader, "tex2", TextureUnits::PicaTexture(2));
SetShaderSamplerBinding(shader, "tex_cube", TextureUnits::TextureCube);
SetShaderSamplerBinding(shader, "tex_normal", TextureUnits::TextureNormalMap);
// Set the texture samplers to correspond to different lookup table texture units
SetShaderSamplerBinding(shader, "texture_buffer_lut_lf", TextureUnits::TextureBufferLUT_LF);
@ -415,8 +416,8 @@ void ShaderProgramManager::UseTrivialGeometryShader() {
impl->current.gs_hash = 0;
}
void ShaderProgramManager::UseFragmentShader(const Pica::Regs& regs) {
PicaFSConfig config = PicaFSConfig::BuildFromRegs(regs);
void ShaderProgramManager::UseFragmentShader(const Pica::Regs& regs, bool use_normal) {
PicaFSConfig config = PicaFSConfig::BuildFromRegs(regs, use_normal);
auto [handle, result] = impl->fragment_shaders.Get(config);
impl->current.fs = handle;
impl->current.fs_hash = config.Hash();

View file

@ -41,7 +41,7 @@ public:
void UseTrivialGeometryShader();
void UseFragmentShader(const Pica::Regs& config);
void UseFragmentShader(const Pica::Regs& config, bool use_normal);
void ApplyTo(OpenGLState& state);

View file

@ -26,6 +26,7 @@ constexpr TextureUnit TextureCube{6};
constexpr TextureUnit TextureBufferLUT_LF{3};
constexpr TextureUnit TextureBufferLUT_RG{4};
constexpr TextureUnit TextureBufferLUT_RGBA{5};
constexpr TextureUnit TextureNormalMap{7};
} // namespace TextureUnits

View file

@ -3,7 +3,7 @@
// Refer to the license.txt file included.
#include "common/scope_exit.h"
#include "common/settings.h"
#include "video_core/custom_textures/material.h"
#include "video_core/regs.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/gl_driver.h"
@ -14,8 +14,10 @@ namespace OpenGL {
namespace {
using VideoCore::MapType;
using VideoCore::PixelFormat;
using VideoCore::SurfaceType;
using VideoCore::TextureType;
constexpr FormatTuple DEFAULT_TUPLE = {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE};
@ -42,6 +44,17 @@ static constexpr std::array<FormatTuple, 5> COLOR_TUPLES_OES = {{
{GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, // RGBA4
}};
static constexpr std::array<FormatTuple, 8> CUSTOM_TUPLES = {{
DEFAULT_TUPLE,
{GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_UNSIGNED_BYTE},
{GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_UNSIGNED_BYTE},
{GL_COMPRESSED_RG_RGTC2, GL_COMPRESSED_RG_RGTC2, GL_UNSIGNED_BYTE},
{GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_UNSIGNED_BYTE},
{GL_COMPRESSED_RGBA_ASTC_4x4, GL_COMPRESSED_RGBA_ASTC_4x4, GL_UNSIGNED_BYTE},
{GL_COMPRESSED_RGBA_ASTC_6x6, GL_COMPRESSED_RGBA_ASTC_6x6, GL_UNSIGNED_BYTE},
{GL_COMPRESSED_RGBA_ASTC_8x6, GL_COMPRESSED_RGBA_ASTC_8x6, GL_UNSIGNED_BYTE},
}};
struct FramebufferInfo {
GLuint color;
GLuint depth;
@ -109,17 +122,23 @@ TextureRuntime::TextureRuntime(const Driver& driver_, VideoCore::RendererBase& r
read_fbos[i].Create();
}
auto Register = [this](PixelFormat dest, std::unique_ptr<FormatReinterpreterBase>&& obj) {
auto add_reinterpreter = [this](PixelFormat dest,
std::unique_ptr<FormatReinterpreterBase>&& obj) {
const u32 dst_index = static_cast<u32>(dest);
return reinterpreters[dst_index].push_back(std::move(obj));
};
Register(PixelFormat::RGBA8, std::make_unique<ShaderD24S8toRGBA8>());
Register(PixelFormat::RGB5A1, std::make_unique<RGBA4toRGB5A1>());
add_reinterpreter(PixelFormat::RGBA8, std::make_unique<ShaderD24S8toRGBA8>());
add_reinterpreter(PixelFormat::RGB5A1, std::make_unique<RGBA4toRGB5A1>());
}
TextureRuntime::~TextureRuntime() = default;
void TextureRuntime::Reset() {
alloc_cache.clear();
framebuffer_cache.clear();
}
bool TextureRuntime::NeedsConversion(VideoCore::PixelFormat pixel_format) const {
const bool should_convert = pixel_format == PixelFormat::RGBA8 || // Needs byteswap
pixel_format == PixelFormat::RGB8; // Is converted to RGBA8
@ -153,51 +172,64 @@ const FormatTuple& TextureRuntime::GetFormatTuple(PixelFormat pixel_format) cons
return DEFAULT_TUPLE;
}
void TextureRuntime::Recycle(const HostTextureTag tag, Allocation&& alloc) {
recycler.emplace(tag, std::move(alloc));
const FormatTuple& TextureRuntime::GetFormatTuple(VideoCore::CustomPixelFormat pixel_format) {
const std::size_t format_index = static_cast<std::size_t>(pixel_format);
return CUSTOM_TUPLES[format_index];
}
Allocation TextureRuntime::Allocate(const VideoCore::SurfaceParams& params) {
const u32 width = params.width;
const u32 height = params.height;
const u32 levels = params.levels;
void TextureRuntime::Recycle(const HostTextureTag tag, Allocation&& alloc) {
alloc_cache.emplace(tag, std::move(alloc));
}
Allocation TextureRuntime::Allocate(const VideoCore::SurfaceParams& params,
const VideoCore::Material* material) {
const GLenum target = params.texture_type == VideoCore::TextureType::CubeMap
? GL_TEXTURE_CUBE_MAP
: GL_TEXTURE_2D;
const auto& tuple = GetFormatTuple(params.pixel_format);
const bool is_custom = material != nullptr;
const bool has_normal = material && material->Map(MapType::Normal);
const auto& tuple =
is_custom ? GetFormatTuple(params.custom_format) : GetFormatTuple(params.pixel_format);
const HostTextureTag key = {
.width = params.width,
.height = params.height,
.levels = params.levels,
.res_scale = params.res_scale,
.tuple = tuple,
.type = params.texture_type,
.width = width,
.height = height,
.levels = levels,
.res_scale = params.res_scale,
.is_custom = is_custom,
.has_normal = has_normal,
};
if (auto it = recycler.find(key); it != recycler.end()) {
Allocation alloc = std::move(it->second);
ASSERT(alloc.res_scale == params.res_scale);
recycler.erase(it);
if (auto it = alloc_cache.find(key); it != alloc_cache.end()) {
auto alloc{std::move(it->second)};
alloc_cache.erase(it);
return alloc;
}
const GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d;
glActiveTexture(GL_TEXTURE0);
std::array<OGLTexture, 2> textures{};
std::array<GLuint, 2> handles{};
std::array<OGLTexture, 3> textures{};
std::array<GLuint, 3> handles{};
textures[0] = MakeHandle(target, width, height, levels, tuple, params.DebugName(false));
textures[0] = MakeHandle(target, params.width, params.height, params.levels, tuple,
params.DebugName(false));
handles.fill(textures[0].handle);
if (params.res_scale != 1) {
const u32 scaled_width = params.GetScaledWidth();
const u32 scaled_height = params.GetScaledHeight();
textures[1] =
MakeHandle(target, scaled_width, scaled_height, levels, tuple, params.DebugName(true));
const u32 scaled_width = is_custom ? params.width : params.GetScaledWidth();
const u32 scaled_height = is_custom ? params.height : params.GetScaledHeight();
const auto& scaled_tuple = is_custom ? GetFormatTuple(PixelFormat::RGBA8) : tuple;
textures[1] = MakeHandle(target, scaled_width, scaled_height, params.levels, scaled_tuple,
params.DebugName(true, is_custom));
handles[1] = textures[1].handle;
}
if (has_normal) {
textures[2] = MakeHandle(target, params.width, params.height, params.levels, tuple,
params.DebugName(true, is_custom));
handles[2] = textures[2].handle;
}
glBindTexture(GL_TEXTURE_2D, old_tex);
@ -311,15 +343,20 @@ bool TextureRuntime::BlitTextures(Surface& source, Surface& dest,
return true;
}
void TextureRuntime::GenerateMipmaps(Surface& surface, u32 max_level) {
void TextureRuntime::GenerateMipmaps(Surface& surface) {
OpenGLState state = OpenGLState::GetCurState();
state.texture_units[0].texture_2d = surface.Handle();
state.Apply();
glActiveTexture(GL_TEXTURE0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, max_level);
const auto generate = [&](u32 index) {
state.texture_units[0].texture_2d = surface.Handle(index);
state.Apply();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, surface.levels - 1);
glGenerateMipmap(GL_TEXTURE_2D);
};
glGenerateMipmap(GL_TEXTURE_2D);
generate(1);
if (surface.HasNormalMap()) {
generate(2);
}
}
const ReinterpreterList& TextureRuntime::GetPossibleReinterpretations(
@ -340,21 +377,11 @@ Surface::~Surface() {
if (pixel_format == PixelFormat::Invalid || !alloc) {
return;
}
const HostTextureTag tag = {
.tuple = alloc.tuple,
.type = texture_type,
.width = alloc.width,
.height = alloc.height,
.levels = alloc.levels,
.res_scale = alloc.res_scale,
};
runtime->Recycle(tag, std::move(alloc));
runtime->Recycle(MakeTag(), std::move(alloc));
}
void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
const VideoCore::StagingData& staging) {
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
ASSERT(stride * GetFormatBytesPerPixel(pixel_format) % 4 == 0);
const GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d;
@ -362,12 +389,10 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
const u32 unscaled_height = upload.texture_rect.GetHeight();
glPixelStorei(GL_UNPACK_ROW_LENGTH, unscaled_width);
// Bind the unscaled texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, Handle(false));
glBindTexture(GL_TEXTURE_2D, Handle(0));
// Upload the requested rectangle of pixels
const auto& tuple = runtime->GetFormatTuple(pixel_format);
const auto& tuple = alloc.tuple;
glTexSubImage2D(GL_TEXTURE_2D, upload.texture_level, upload.texture_rect.left,
upload.texture_rect.bottom, unscaled_width, unscaled_height, tuple.format,
tuple.type, staging.mapped.data());
@ -381,17 +406,61 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
.src_rect = upload.texture_rect,
.dst_rect = upload.texture_rect * res_scale,
};
// If texture filtering is enabled attempt to upscale with that, otherwise fallback
// to normal blit.
if (res_scale != 1 && !runtime->blit_helper.Filter(*this, blit)) {
BlitScale(blit, true);
}
}
void Surface::UploadCustom(const VideoCore::Material* material, u32 level) {
const GLuint old_tex = OpenGLState::GetCurState().texture_units[0].texture_2d;
const auto& tuple = alloc.tuple;
const u32 width = material->width;
const u32 height = material->height;
const auto color = material->textures[0];
const Common::Rectangle filter_rect{0U, height, width, 0U};
glActiveTexture(GL_TEXTURE0);
glPixelStorei(GL_UNPACK_ROW_LENGTH, width);
glBindTexture(GL_TEXTURE_2D, Handle(0));
if (VideoCore::IsCustomFormatCompressed(custom_format)) {
const GLsizei image_size = static_cast<GLsizei>(color->data.size());
glCompressedTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, width, height, tuple.format,
image_size, color->data.data());
} else {
glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, width, height, tuple.format, tuple.type,
color->data.data());
}
const VideoCore::TextureBlit blit = {
.src_rect = filter_rect,
.dst_rect = filter_rect,
};
if (res_scale != 1 && !runtime->blit_helper.Filter(*this, blit)) {
BlitScale(blit, true);
}
for (u32 i = 1; i < VideoCore::MAX_MAPS; i++) {
const auto texture = material->textures[i];
if (!texture) {
continue;
}
glBindTexture(GL_TEXTURE_2D, Handle(i + 1));
if (VideoCore::IsCustomFormatCompressed(custom_format)) {
const GLsizei image_size = static_cast<GLsizei>(texture->data.size());
glCompressedTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, width, height, tuple.format,
image_size, texture->data.data());
} else {
glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, width, height, tuple.format, tuple.type,
texture->data.data());
}
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
glBindTexture(GL_TEXTURE_2D, old_tex);
}
void Surface::Download(const VideoCore::BufferTextureCopy& download,
const VideoCore::StagingData& staging) {
// Ensure no bad interactions with GL_PACK_ALIGNMENT
ASSERT(stride * GetFormatBytesPerPixel(pixel_format) % 4 == 0);
const u32 unscaled_width = download.texture_rect.GetWidth();
@ -414,11 +483,9 @@ void Surface::Download(const VideoCore::BufferTextureCopy& download,
return;
}
const u32 fbo_index = FboIndex(type);
OpenGLState state = OpenGLState::GetCurState();
state.scissor.enabled = false;
state.draw.read_framebuffer = runtime->read_fbos[fbo_index].handle;
state.draw.read_framebuffer = runtime->read_fbos[FboIndex(type)].handle;
state.Apply();
Attach(GL_READ_FRAMEBUFFER, download.texture_level, 0, false);
@ -428,14 +495,11 @@ void Surface::Download(const VideoCore::BufferTextureCopy& download,
glReadPixels(download.texture_rect.left, download.texture_rect.bottom, unscaled_width,
unscaled_height, tuple.format, tuple.type, staging.mapped.data());
// Restore previous state
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
}
bool Surface::DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download,
const VideoCore::StagingData& staging) {
// If a partial download is requested and ARB_get_texture_sub_image (core in 4.5)
// is not available we cannot proceed further.
const bool is_full_download = download.texture_rect == GetRect();
const bool has_sub_image = driver->HasArbGetTextureSubImage();
if (driver->IsOpenGLES() || (!is_full_download && !has_sub_image)) {
@ -452,7 +516,7 @@ bool Surface::DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download,
// Prefer glGetTextureSubImage in most cases since it's the fastest and most convenient option
if (has_sub_image) {
const GLsizei buf_size = static_cast<GLsizei>(staging.mapped.size());
glGetTextureSubImage(Handle(false), download.texture_level, download.texture_rect.left,
glGetTextureSubImage(Handle(0), download.texture_level, download.texture_rect.left,
download.texture_rect.bottom, 0, download.texture_rect.GetWidth(),
download.texture_rect.GetHeight(), 1, tuple.format, tuple.type,
buf_size, staging.mapped.data());
@ -461,7 +525,7 @@ bool Surface::DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download,
// This should only trigger for full texture downloads in oldish intel drivers
// that only support up to 4.3
glBindTexture(GL_TEXTURE_2D, Handle(false));
glBindTexture(GL_TEXTURE_2D, Handle(0));
glGetTexImage(GL_TEXTURE_2D, download.texture_level, tuple.format, tuple.type,
staging.mapped.data());
glBindTexture(GL_TEXTURE_2D, old_tex);
@ -470,20 +534,20 @@ bool Surface::DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download,
}
void Surface::Attach(GLenum target, u32 level, u32 layer, bool scaled) {
const GLuint handle = Handle(scaled);
const GLenum textarget = texture_type == VideoCore::TextureType::CubeMap
const GLuint handle = Handle(static_cast<u32>(scaled));
const GLenum textarget = texture_type == TextureType::CubeMap
? GL_TEXTURE_CUBE_MAP_POSITIVE_X + layer
: GL_TEXTURE_2D;
switch (type) {
case VideoCore::SurfaceType::Color:
case VideoCore::SurfaceType::Texture:
case SurfaceType::Color:
case SurfaceType::Texture:
glFramebufferTexture2D(target, GL_COLOR_ATTACHMENT0, textarget, handle, level);
break;
case VideoCore::SurfaceType::Depth:
case SurfaceType::Depth:
glFramebufferTexture2D(target, GL_DEPTH_ATTACHMENT, textarget, handle, level);
break;
case VideoCore::SurfaceType::DepthStencil:
case SurfaceType::DepthStencil:
glFramebufferTexture2D(target, GL_DEPTH_STENCIL_ATTACHMENT, textarget, handle, level);
break;
default:
@ -491,6 +555,30 @@ void Surface::Attach(GLenum target, u32 level, u32 layer, bool scaled) {
}
}
bool Surface::Swap(const VideoCore::Material* mat) {
const VideoCore::CustomPixelFormat format{mat->format};
if (!driver->IsCustomFormatSupported(format)) {
return false;
}
runtime->Recycle(MakeTag(), std::move(alloc));
SurfaceParams params = *this;
params.width = mat->width;
params.height = mat->height;
params.custom_format = mat->format;
alloc = runtime->Allocate(params, mat);
LOG_DEBUG(Render_OpenGL, "Swapped {}x{} {} surface at address {:#x} to {}x{} {}",
GetScaledWidth(), GetScaledHeight(), VideoCore::PixelFormatAsString(pixel_format),
addr, width, height, VideoCore::CustomPixelFormatAsString(format));
is_custom = true;
custom_format = format;
material = mat;
return true;
}
u32 Surface::GetInternalBytesPerPixel() const {
// RGB8 is converted to RGBA8 on OpenGL ES since it doesn't support BGR8
if (driver->IsOpenGLES() && pixel_format == VideoCore::PixelFormat::RGB8) {
@ -518,6 +606,19 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
blit.dst_rect.right, blit.dst_rect.top, buffer_mask, filter);
}
HostTextureTag Surface::MakeTag() const noexcept {
return HostTextureTag{
.width = alloc.width,
.height = alloc.height,
.levels = alloc.levels,
.res_scale = alloc.res_scale,
.tuple = alloc.tuple,
.type = texture_type,
.is_custom = is_custom,
.has_normal = HasNormalMap(),
};
}
Framebuffer::Framebuffer(TextureRuntime& runtime, Surface* const color, u32 color_level,
Surface* const depth_stencil, u32 depth_level, const Pica::Regs& regs,
Common::Rectangle<u32> surfaces_rect)
@ -527,7 +628,7 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, Surface* const color, u32 colo
const bool shadow_rendering = regs.framebuffer.IsShadowRendering();
const bool has_stencil = regs.framebuffer.HasStencil();
if (shadow_rendering && !color) {
return; // Framebuffer won't get used
return;
}
if (color) {
@ -574,19 +675,15 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, Surface* const color, u32 colo
color ? color->Handle() : 0, color_level);
if (depth_stencil) {
if (has_stencil) {
// Attach both depth and stencil
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
GL_TEXTURE_2D, depth_stencil->Handle(), depth_level);
} else {
// Attach depth
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D,
depth_stencil->Handle(), depth_level);
// Clear stencil attachment
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
0);
}
} else {
// Clear both depth and stencil attachment
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
0, 0);
}

View file

@ -10,8 +10,9 @@
#include "video_core/renderer_opengl/gl_format_reinterpreter.h"
namespace VideoCore {
struct Material;
class RendererBase;
}
} // namespace VideoCore
namespace OpenGL {
@ -27,17 +28,19 @@ struct FormatTuple {
};
struct HostTextureTag {
FormatTuple tuple{};
VideoCore::TextureType type{};
u32 width = 0;
u32 height = 0;
u32 levels = 1;
u32 res_scale = 1;
u32 width;
u32 height;
u32 levels;
u32 res_scale;
FormatTuple tuple;
VideoCore::TextureType type;
bool is_custom;
bool has_normal;
bool operator==(const HostTextureTag& other) const noexcept {
return std::tie(tuple, type, width, height, levels, res_scale) ==
return std::tie(tuple, type, width, height, levels, res_scale, is_custom, has_normal) ==
std::tie(other.tuple, other.type, other.width, other.height, other.levels,
other.res_scale);
other.res_scale, other.is_custom, other.has_normal);
}
struct Hash {
@ -46,10 +49,12 @@ struct HostTextureTag {
}
};
};
static_assert(std::has_unique_object_representations_v<HostTextureTag>,
"HostTextureTag is not suitable for hashing!");
struct Allocation {
std::array<OGLTexture, 2> textures;
std::array<GLuint, 2> handles;
std::array<OGLTexture, 3> textures;
std::array<GLuint, 3> handles;
FormatTuple tuple;
u32 width;
u32 height;
@ -59,15 +64,10 @@ struct Allocation {
operator bool() const noexcept {
return textures[0].handle;
}
bool Matches(u32 width_, u32 height_, u32 levels_, const FormatTuple& tuple_) const {
return std::tie(width, height, levels, tuple) == std::tie(width_, height_, levels_, tuple_);
}
};
class Surface;
class Driver;
struct CachedTextureCube;
/**
* Provides texture manipulation functions to the rasterizer cache
@ -82,6 +82,9 @@ public:
explicit TextureRuntime(const Driver& driver, VideoCore::RendererBase& renderer);
~TextureRuntime();
/// Clears all cached runtime resources
void Reset();
/// Returns true if the provided pixel format cannot be used natively by the runtime.
bool NeedsConversion(VideoCore::PixelFormat pixel_format) const;
@ -90,12 +93,14 @@ public:
/// Returns the OpenGL format tuple associated with the provided pixel format
const FormatTuple& GetFormatTuple(VideoCore::PixelFormat pixel_format) const;
const FormatTuple& GetFormatTuple(VideoCore::CustomPixelFormat pixel_format);
/// Takes back ownership of the allocation for recycling
void Recycle(const HostTextureTag tag, Allocation&& alloc);
/// Allocates an OpenGL texture with the specified dimentions and format
Allocation Allocate(const VideoCore::SurfaceParams& params);
/// Allocates a texture with the specified dimentions and format
Allocation Allocate(const VideoCore::SurfaceParams& params,
const VideoCore::Material* material = nullptr);
/// Fills the rectangle of the texture with the clear value provided
bool ClearTexture(Surface& surface, const VideoCore::TextureClear& clear);
@ -107,7 +112,7 @@ public:
bool BlitTextures(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit);
/// Generates mipmaps for all the available levels of the texture
void GenerateMipmaps(Surface& surface, u32 max_level);
void GenerateMipmaps(Surface& surface);
/// Returns all source formats that support reinterpretation to the dest format
const ReinterpreterList& GetPossibleReinterpretations(VideoCore::PixelFormat dest_format) const;
@ -123,7 +128,7 @@ private:
BlitHelper blit_helper;
std::vector<u8> staging_buffer;
std::array<ReinterpreterList, VideoCore::PIXEL_FORMAT_COUNT> reinterpreters;
std::unordered_multimap<HostTextureTag, Allocation, HostTextureTag::Hash> recycler;
std::unordered_multimap<HostTextureTag, Allocation, HostTextureTag::Hash> alloc_cache;
std::unordered_map<u64, OGLFramebuffer, Common::IdentityHash<u64>> framebuffer_cache;
std::array<OGLFramebuffer, 3> draw_fbos;
std::array<OGLFramebuffer, 3> read_fbos;
@ -140,9 +145,9 @@ public:
Surface(Surface&& o) noexcept = default;
Surface& operator=(Surface&& o) noexcept = default;
/// Returns the surface image handle
GLuint Handle(bool scaled = true) const noexcept {
return alloc.handles[static_cast<u32>(scaled)];
/// Returns the surface image handle at the provided index.
GLuint Handle(u32 index = 1) const noexcept {
return alloc.handles[index];
}
/// Returns the tuple of the surface allocation.
@ -150,9 +155,20 @@ public:
return alloc.tuple;
}
/// Returns the extent of the underlying surface allocation
VideoCore::Extent Extent() const noexcept {
return {
.width = alloc.width,
.height = alloc.height,
};
}
/// Uploads pixel data in staging to a rectangle region of the surface texture
void Upload(const VideoCore::BufferTextureCopy& upload, const VideoCore::StagingData& staging);
/// Uploads the custom material to the surface allocation.
void UploadCustom(const VideoCore::Material* material, u32 level);
/// Downloads pixel data to staging from a rectangle region of the surface texture
void Download(const VideoCore::BufferTextureCopy& download,
const VideoCore::StagingData& staging);
@ -160,6 +176,9 @@ public:
/// Attaches a handle of surface to the specified framebuffer target
void Attach(GLenum target, u32 level, u32 layer, bool scaled = true);
/// Swaps the internal allocation to match the provided material
bool Swap(const VideoCore::Material* material);
/// Returns the bpp of the internal surface format
u32 GetInternalBytesPerPixel() const;
@ -171,6 +190,9 @@ private:
bool DownloadWithoutFbo(const VideoCore::BufferTextureCopy& download,
const VideoCore::StagingData& staging);
/// Returns the texture tag of the current allocation
HostTextureTag MakeTag() const noexcept;
private:
const Driver* driver;
TextureRuntime* runtime;

View file

@ -313,7 +313,8 @@ RendererOpenGL::RendererOpenGL(Core::System& system, Frontend::EmuWindow& window
}
frame_dumper.mailbox = std::make_unique<OGLVideoDumpingMailbox>();
InitOpenGLObjects();
rasterizer = std::make_unique<RasterizerOpenGL>(system.Memory(), *this, driver);
rasterizer = std::make_unique<RasterizerOpenGL>(system.Memory(), system.CustomTexManager(),
*this, driver);
}
RendererOpenGL::~RendererOpenGL() = default;
@ -347,6 +348,8 @@ void RendererOpenGL::SwapBuffers() {
EndFrame();
prev_state.Apply();
rasterizer->TickFrame();
}
void RendererOpenGL::RenderScreenshot() {
@ -1169,9 +1172,6 @@ void RendererOpenGL::TryPresent(int timeout_ms, bool is_secondary) {
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
}
/// Updates the framerate
void RendererOpenGL::UpdateFramerate() {}
void RendererOpenGL::PrepareVideoDumping() {
auto* mailbox = static_cast<OGLVideoDumpingMailbox*>(frame_dumper.mailbox.get());
{

View file

@ -94,7 +94,6 @@ private:
float h);
void DrawSingleScreenStereo(const ScreenInfo& screen_info_l, const ScreenInfo& screen_info_r,
float x, float y, float w, float h);
void UpdateFramerate();
// Loads framebuffer from emulated memory into the display information structure
void LoadFBToScreenInfo(const GPU::Regs::FramebufferConfig& framebuffer,