GPU: Implement resolution scaling

This commit is contained in:
Connor McLaughlin 2019-10-03 16:46:13 +10:00
parent 1f6130f04a
commit 48563c74cf
4 changed files with 202 additions and 63 deletions

View file

@ -134,10 +134,10 @@ void GPU_HW::LoadVertices(RenderCommand rc, u32 num_vertices)
void GPU_HW::CalcScissorRect(int* left, int* top, int* right, int* bottom)
{
*left = m_drawing_area.left;
*right = m_drawing_area.right + 1;
*top = m_drawing_area.top;
*bottom = m_drawing_area.bottom + 1;
*left = m_drawing_area.left * s32(m_resolution_scale);
*right = (m_drawing_area.right + 1) * s32(m_resolution_scale);
*top = m_drawing_area.top * s32(m_resolution_scale);
*bottom = (m_drawing_area.bottom + 1) * s32(m_resolution_scale);
}
static void DefineMacro(std::stringstream& ss, const char* name, bool enabled)
@ -151,8 +151,8 @@ static void DefineMacro(std::stringstream& ss, const char* name, bool enabled)
void GPU_HW::GenerateShaderHeader(std::stringstream& ss)
{
ss << "#version 330 core\n\n";
ss << "const ivec2 VRAM_SIZE = ivec2(" << VRAM_WIDTH << ", " << VRAM_HEIGHT << ");\n";
ss << "const ivec2 VRAM_COORD_MASK = ivec2(" << (VRAM_WIDTH - 1) << ", " << (VRAM_HEIGHT - 1) << ");\n";
ss << "const int RESOLUTION_SCALE = " << m_resolution_scale << ";\n";
ss << "const ivec2 VRAM_SIZE = ivec2(" << VRAM_WIDTH << ", " << VRAM_HEIGHT << ") * RESOLUTION_SCALE;\n";
ss << "const vec2 RCP_VRAM_SIZE = vec2(1.0, 1.0) / vec2(VRAM_SIZE);\n";
ss << R"(
@ -269,11 +269,11 @@ vec4 SampleFromVRAM(vec2 coord)
#endif
// fixup coords
ivec2 vicoord = ivec2(v_texpage.x + index_coord.x,
fixYCoord(v_texpage.y + index_coord.y));
ivec2 vicoord = ivec2((v_texpage.x + index_coord.x) * RESOLUTION_SCALE,
fixYCoord((v_texpage.y + index_coord.y) * RESOLUTION_SCALE));
// load colour/palette
vec4 color = texelFetch(samp0, vicoord & VRAM_COORD_MASK, 0);
vec4 color = texelFetch(samp0, vicoord, 0);
// apply palette
#if PALETTE
@ -286,8 +286,9 @@ vec4 SampleFromVRAM(vec2 coord)
uint vram_value = RGBA8ToRGBA5551(color);
int palette_index = int((vram_value >> (subpixel * 8)) & 0xFFu);
#endif
ivec2 palette_icoord = ivec2(v_texpage.z + palette_index, fixYCoord(v_texpage.w));
color = texelFetch(samp0, palette_icoord & VRAM_COORD_MASK, 0);
ivec2 palette_icoord = ivec2((v_texpage.z + palette_index) * RESOLUTION_SCALE,
fixYCoord(v_texpage.w * RESOLUTION_SCALE));
color = texelFetch(samp0, palette_icoord, 0);
#endif
return color;

View file

@ -2,6 +2,7 @@
#include "gpu.h"
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
class GPU_HW : public GPU
@ -75,11 +76,18 @@ protected:
void CalcScissorRect(int* left, int* top, int* right, int* bottom);
std::tuple<s32, s32> ScaleVRAMCoordinates(s32 x, s32 y) const
{
return std::make_tuple(x * s32(m_resolution_scale), y * s32(m_resolution_scale));
}
std::string GenerateVertexShader(bool textured);
std::string GenerateFragmentShader(bool textured, bool blending, bool transparent, TextureColorMode texture_color_mode);
std::string GenerateFragmentShader(bool textured, bool blending, bool transparent,
TextureColorMode texture_color_mode);
std::string GenerateScreenQuadVertexShader();
std::string GenerateFillFragmentShader();
u32 m_resolution_scale = 1;
HWRenderBatch m_batch = {};
private:

View file

@ -1,6 +1,7 @@
#include "gpu_hw_opengl.h"
#include "YBaseLib/Assert.h"
#include "YBaseLib/Log.h"
#include "YBaseLib/String.h"
#include "host_interface.h"
#include "imgui.h"
#include "system.h"
@ -75,6 +76,24 @@ void GPU_HW_OpenGL::RenderUI()
ImGui::Columns(1);
ImGui::Checkbox("Show VRAM##gpu_gl_show_vram", &m_show_vram);
static constexpr std::array<const char*, 16> internal_resolution_items = {
{"1x Internal Resolution", "2x Internal Resolution", "3x Internal Resolution", "4x Internal Resolution",
"5x Internal Resolution", "6x Internal Resolution", "7x Internal Resolution", "8x Internal Resolution",
"9x Internal Resolution", "10x Internal Resolution", "11x Internal Resolution", "12x Internal Resolution",
"13x Internal Resolution", "14x Internal Resolution", "15x Internal Resolution", "16x Internal Resolution"}};
int internal_resolution_item =
std::clamp(static_cast<int>(m_resolution_scale) - 1, 0, static_cast<int>(internal_resolution_items.size()));
if (ImGui::Combo("##gpu_internal_resolution", &internal_resolution_item, internal_resolution_items.data(),
static_cast<int>(internal_resolution_items.size())))
{
m_resolution_scale = static_cast<u32>(internal_resolution_item + 1);
m_system->GetHostInterface()->AddOSDMessage(
TinyString::FromFormat("Internal resolution changed to %ux, recompiling programs", m_resolution_scale));
CreateFramebuffer();
CompilePrograms();
}
}
ImGui::End();
@ -92,27 +111,67 @@ std::tuple<s32, s32> GPU_HW_OpenGL::ConvertToFramebufferCoordinates(s32 x, s32 y
void GPU_HW_OpenGL::CreateFramebuffer()
{
m_framebuffer_texture =
std::make_unique<GL::Texture>(VRAM_WIDTH, VRAM_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, false);
// save old vram texture/fbo, in case we're changing scale
auto old_vram_texture = std::move(m_vram_texture);
const GLuint old_vram_fbo = m_vram_fbo;
m_vram_fbo = 0;
DestroyFramebuffer();
glGenFramebuffers(1, &m_framebuffer_fbo_id);
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer_fbo_id);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_framebuffer_texture->GetGLId(), 0);
// scale vram size to internal resolution
const u32 texture_width = VRAM_WIDTH * m_resolution_scale;
const u32 texture_height = VRAM_HEIGHT * m_resolution_scale;
m_vram_texture =
std::make_unique<GL::Texture>(texture_width, texture_height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, false);
glGenFramebuffers(1, &m_vram_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, m_vram_fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_vram_texture->GetGLId(), 0);
Assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
// do we need to restore the framebuffer after a size change?
if (old_vram_texture)
{
const bool linear_filter = old_vram_texture->GetWidth() > m_vram_texture->GetWidth();
Log_DevPrintf("Scaling %ux%u VRAM texture to %ux%u using %s filter", old_vram_texture->GetWidth(),
old_vram_texture->GetHeight(), m_vram_texture->GetWidth(), m_vram_texture->GetHeight(),
linear_filter ? "linear" : "nearest");
glDisable(GL_SCISSOR_TEST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, old_vram_fbo);
glBlitFramebuffer(0, 0, old_vram_texture->GetWidth(), old_vram_texture->GetHeight(), 0, 0,
m_vram_texture->GetWidth(), m_vram_texture->GetHeight(), GL_COLOR_BUFFER_BIT,
linear_filter ? GL_LINEAR : GL_NEAREST);
glDeleteFramebuffers(1, &old_vram_fbo);
old_vram_texture.reset();
}
m_vram_read_texture =
std::make_unique<GL::Texture>(VRAM_WIDTH, VRAM_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, false);
glGenFramebuffers(1, &m_vram_read_fbo_id);
glBindFramebuffer(GL_FRAMEBUFFER, m_vram_read_fbo_id);
std::make_unique<GL::Texture>(texture_width, texture_height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, false);
glGenFramebuffers(1, &m_vram_read_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, m_vram_read_fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_vram_read_texture->GetGLId(), 0);
Assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
m_display_texture = std::make_unique<GL::Texture>(VRAM_WIDTH, VRAM_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, false);
if (m_resolution_scale > 1)
{
m_vram_downsample_texture =
std::make_unique<GL::Texture>(VRAM_WIDTH, VRAM_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, false);
m_vram_downsample_texture->Bind();
glGenFramebuffers(1, &m_vram_downsample_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, m_vram_downsample_fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_vram_downsample_texture->GetGLId(),
0);
Assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
}
m_display_texture =
std::make_unique<GL::Texture>(texture_width, texture_height, GL_RGBA, GL_UNSIGNED_BYTE, nullptr, false);
m_display_texture->Bind();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glGenFramebuffers(1, &m_display_fbo_id);
glBindFramebuffer(GL_FRAMEBUFFER, m_display_fbo_id);
glGenFramebuffers(1, &m_display_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, m_display_fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_display_texture->GetGLId(), 0);
Assert(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
}
@ -120,7 +179,7 @@ void GPU_HW_OpenGL::CreateFramebuffer()
void GPU_HW_OpenGL::ClearFramebuffer()
{
// TODO: get rid of the FBO switches
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer_fbo_id);
glBindFramebuffer(GL_FRAMEBUFFER, m_vram_fbo);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
@ -128,13 +187,24 @@ void GPU_HW_OpenGL::ClearFramebuffer()
void GPU_HW_OpenGL::DestroyFramebuffer()
{
glDeleteFramebuffers(1, &m_vram_read_fbo_id);
m_vram_read_fbo_id = 0;
glDeleteFramebuffers(1, &m_vram_read_fbo);
m_vram_read_fbo = 0;
m_vram_read_texture.reset();
glDeleteFramebuffers(1, &m_framebuffer_fbo_id);
m_framebuffer_fbo_id = 0;
m_framebuffer_texture.reset();
glDeleteFramebuffers(1, &m_vram_fbo);
m_vram_fbo = 0;
m_vram_texture.reset();
if (m_vram_downsample_texture)
{
glDeleteFramebuffers(1, &m_vram_downsample_fbo);
m_vram_downsample_fbo = 0;
m_vram_downsample_texture.reset();
}
glDeleteFramebuffers(1, &m_display_fbo);
m_display_fbo = 0;
m_display_texture.reset();
}
void GPU_HW_OpenGL::CreateVertexBuffer()
@ -246,7 +316,7 @@ void GPU_HW_OpenGL::SetProgram()
void GPU_HW_OpenGL::SetViewport()
{
glViewport(0, 0, VRAM_WIDTH, VRAM_HEIGHT);
glViewport(0, 0, m_vram_texture->GetWidth(), m_vram_texture->GetHeight());
}
void GPU_HW_OpenGL::SetScissor()
@ -257,7 +327,7 @@ void GPU_HW_OpenGL::SetScissor()
const int width = right - left;
const int height = bottom - top;
const int x = left;
const int y = VRAM_HEIGHT - bottom;
const int y = m_vram_texture->GetHeight() - bottom;
Log_DebugPrintf("SetScissor: (%d-%d, %d-%d)", x, x + width, y, y + height);
glScissor(x, y, width, height);
@ -283,24 +353,27 @@ void GPU_HW_OpenGL::UpdateDisplay()
{
GPU_HW::UpdateDisplay();
const u32 texture_width = m_vram_texture->GetWidth();
const u32 texture_height = m_vram_texture->GetHeight();
// TODO: 24-bit support.
if (m_show_vram)
{
m_system->GetHostInterface()->SetDisplayTexture(m_framebuffer_texture.get(), 0, 0, VRAM_WIDTH, VRAM_HEIGHT, 1.0f);
m_system->GetHostInterface()->SetDisplayTexture(m_vram_texture.get(), 0, 0, texture_width, texture_height, 1.0f);
}
else
{
const u32 display_width = m_crtc_state.horizontal_resolution;
const u32 display_height = m_crtc_state.vertical_resolution;
const u32 vram_offset_x = m_crtc_state.regs.X;
const u32 vram_offset_y = m_crtc_state.regs.Y;
const u32 display_width = m_crtc_state.horizontal_resolution * m_resolution_scale;
const u32 display_height = m_crtc_state.vertical_resolution * m_resolution_scale;
const u32 vram_offset_x = m_crtc_state.regs.X * m_resolution_scale;
const u32 vram_offset_y = m_crtc_state.regs.Y * m_resolution_scale;
const u32 copy_width =
((vram_offset_x + display_width) > VRAM_WIDTH) ? (VRAM_WIDTH - vram_offset_x) : display_width;
((vram_offset_x + display_width) > texture_width) ? (texture_width - vram_offset_x) : display_width;
const u32 copy_height =
((vram_offset_y + display_height) > VRAM_HEIGHT) ? (VRAM_HEIGHT - vram_offset_y) : display_height;
glCopyImageSubData(m_framebuffer_texture->GetGLId(), GL_TEXTURE_2D, 0, vram_offset_x,
VRAM_HEIGHT - vram_offset_y - copy_height, 0, m_display_texture->GetGLId(), GL_TEXTURE_2D, 0, 0,
0, 0, copy_width, copy_height, 1);
((vram_offset_y + display_height) > texture_height) ? (texture_height - vram_offset_y) : display_height;
glCopyImageSubData(m_vram_texture->GetGLId(), GL_TEXTURE_2D, 0, vram_offset_x,
texture_height - vram_offset_y - copy_height, 0, m_display_texture->GetGLId(), GL_TEXTURE_2D, 0,
0, 0, 0, copy_width, copy_height, 1);
m_system->GetHostInterface()->SetDisplayTexture(m_display_texture.get(), 0, 0, copy_width, copy_height,
DISPLAY_ASPECT_RATIO);
@ -311,8 +384,30 @@ void GPU_HW_OpenGL::ReadVRAM(u32 x, u32 y, u32 width, u32 height, void* buffer)
{
// we need to convert RGBA8 -> RGBA5551
std::vector<u32> temp_buffer(width * height);
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_framebuffer_fbo_id);
glReadPixels(x, VRAM_HEIGHT - y - height, width, height, GL_RGBA, GL_UNSIGNED_BYTE, temp_buffer.data());
// downscaling to 1xIR.
if (m_resolution_scale > 1)
{
const u32 texture_width = m_vram_texture->GetWidth();
const u32 texture_height = m_vram_texture->GetHeight();
const u32 scaled_x = x * m_resolution_scale;
const u32 scaled_y = y * m_resolution_scale;
const u32 scaled_width = width * m_resolution_scale;
const u32 scaled_height = height * m_resolution_scale;
glDisable(GL_SCISSOR_TEST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_vram_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_vram_downsample_fbo);
glBlitFramebuffer(scaled_x, texture_height - scaled_y - height, scaled_x + scaled_width, scaled_y + scaled_height,
0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_LINEAR);
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_vram_downsample_fbo);
glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, temp_buffer.data());
}
else
{
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_vram_fbo);
glReadPixels(x, VRAM_HEIGHT - y - height, width, height, GL_RGBA, GL_UNSIGNED_BYTE, temp_buffer.data());
}
// reverse copy because of lower-left origin
const u32 source_stride = width * sizeof(u32);
@ -342,10 +437,16 @@ void GPU_HW_OpenGL::ReadVRAM(u32 x, u32 y, u32 width, u32 height, void* buffer)
void GPU_HW_OpenGL::FillVRAM(u32 x, u32 y, u32 width, u32 height, u16 color)
{
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer_fbo_id);
// scale coordiantes
x *= m_resolution_scale;
y *= m_resolution_scale;
width *= m_resolution_scale;
height *= m_resolution_scale;
glBindFramebuffer(GL_FRAMEBUFFER, m_vram_fbo);
glEnable(GL_SCISSOR_TEST);
glScissor(x, VRAM_HEIGHT - y - height, width, height);
glScissor(x, m_vram_texture->GetHeight() - y - height, width, height);
const auto [r, g, b, a] = RGBA8ToFloat(RGBA5551ToRGBA8888(color));
glClearColor(r, g, b, a);
@ -379,24 +480,50 @@ void GPU_HW_OpenGL::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void*
source_ptr -= source_stride;
}
m_framebuffer_texture->Bind();
// have to write to the 1x texture first
if (m_resolution_scale > 1)
m_vram_downsample_texture->Bind();
else
m_vram_texture->Bind();
// lower-left origin flip happens here
glTexSubImage2D(GL_TEXTURE_2D, 0, x, VRAM_HEIGHT - y - height, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
rgba_data.data());
const u32 flipped_y = VRAM_HEIGHT - y - height;
// update texture data
glTexSubImage2D(GL_TEXTURE_2D, 0, x, flipped_y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, rgba_data.data());
InvalidateVRAMReadCache();
if (m_resolution_scale > 1)
{
// scale to internal resolution
const u32 scaled_width = width * m_resolution_scale;
const u32 scaled_height = height * m_resolution_scale;
const u32 scaled_x = x * m_resolution_scale;
const u32 scaled_y = y * m_resolution_scale;
const u32 scaled_flipped_y = m_vram_texture->GetHeight() - scaled_y - scaled_height;
glDisable(GL_SCISSOR_TEST);
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_vram_downsample_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_vram_fbo);
glBlitFramebuffer(x, flipped_y, x + width, flipped_y + height, scaled_x, scaled_flipped_y, scaled_x + scaled_width,
scaled_flipped_y + scaled_height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
}
void GPU_HW_OpenGL::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height)
{
glDisable(GL_SCISSOR_TEST);
src_x *= m_resolution_scale;
src_y *= m_resolution_scale;
dst_x *= m_resolution_scale;
dst_y *= m_resolution_scale;
width *= m_resolution_scale;
height *= m_resolution_scale;
// lower-left origin flip
src_y = VRAM_HEIGHT - src_y - height;
dst_y = VRAM_HEIGHT - dst_y - height;
src_y = m_vram_texture->GetHeight() - src_y - height;
dst_y = m_vram_texture->GetHeight() - dst_y - height;
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer_fbo_id);
glDisable(GL_SCISSOR_TEST);
glBindFramebuffer(GL_FRAMEBUFFER, m_vram_fbo);
glBlitFramebuffer(src_x, src_y, src_x + width, src_y + height, dst_x, dst_y, dst_x + width, dst_y + height,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
@ -409,8 +536,8 @@ void GPU_HW_OpenGL::UpdateVRAMReadTexture()
m_vram_read_texture_dirty = false;
// TODO: Fallback blit path, and partial updates.
glCopyImageSubData(m_framebuffer_texture->GetGLId(), GL_TEXTURE_2D, 0, 0, 0, 0, m_vram_read_texture->GetGLId(),
GL_TEXTURE_2D, 0, 0, 0, 0, VRAM_WIDTH, VRAM_HEIGHT, 1);
glCopyImageSubData(m_vram_texture->GetGLId(), GL_TEXTURE_2D, 0, 0, 0, 0, m_vram_read_texture->GetGLId(),
GL_TEXTURE_2D, 0, 0, 0, 0, m_vram_texture->GetWidth(), m_vram_texture->GetHeight(), 1);
}
void GPU_HW_OpenGL::FlushRender()
@ -433,7 +560,7 @@ void GPU_HW_OpenGL::FlushRender()
SetScissor();
SetBlendState();
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer_fbo_id);
glBindFramebuffer(GL_FRAMEBUFFER, m_vram_fbo);
glBindVertexArray(m_vao_id);
Assert((m_batch.vertices.size() * sizeof(HWVertex)) <= VERTEX_BUFFER_SIZE);

View file

@ -51,24 +51,27 @@ private:
void SetScissor();
void SetBlendState();
std::unique_ptr<GL::Texture> m_framebuffer_texture;
GLuint m_framebuffer_fbo_id = 0;
// downsample texture - used for readbacks at >1xIR.
std::unique_ptr<GL::Texture> m_vram_texture;
std::unique_ptr<GL::Texture> m_vram_read_texture;
GLuint m_vram_read_fbo_id = 0;
bool m_vram_read_texture_dirty = true;
std::unique_ptr<GL::Texture> m_vram_downsample_texture;
std::unique_ptr<GL::Texture> m_display_texture;
GLuint m_display_fbo_id = 0;
GLuint m_vram_fbo = 0;
GLuint m_vram_read_fbo = 0;
GLuint m_vram_downsample_fbo = 0;
GLuint m_display_fbo = 0;
GLuint m_vertex_buffer = 0;
GLuint m_vao_id = 0;
GLuint m_attributeless_vao_id = 0;
bool m_vram_read_texture_dirty = true;
std::array<std::array<std::array<std::array<GL::Program, 3>, 2>, 2>, 2> m_render_programs;
std::array<GL::Program, 3> m_texture_page_programs;
GLStats m_stats = {};
GLStats m_last_stats = {};
bool m_show_vram = false;
bool m_show_vram = true;
};