ui: Support widescreen thumbnails

This commit is contained in:
Matt Borgerson 2023-06-03 22:22:24 -07:00 committed by mborgerson
parent 9c9f1e83eb
commit 348b45b436
5 changed files with 93 additions and 62 deletions

View File

@ -28,8 +28,8 @@ extern "C" {
#include "block/snapshot.h"
#define XEMU_SNAPSHOT_DATA_MAGIC 0x78656d75
#define XEMU_SNAPSHOT_HEIGHT 120
#define XEMU_SNAPSHOT_WIDTH 160
#define XEMU_SNAPSHOT_THUMBNAIL_WIDTH 160
#define XEMU_SNAPSHOT_THUMBNAIL_HEIGHT 120
extern const char **g_snapshot_shortcut_index_key_map[];

View File

@ -67,24 +67,15 @@ TextureBuffer *xemu_snapshots_extract_thumbnail()
return NULL;
}
// Render at 2x the base size to account for potential UI scaling
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, display_tex);
int thumbnail_width = XEMU_SNAPSHOT_WIDTH * 2;
int tex_width;
int tex_height;
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tex_width);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &tex_height);
int thumbnail_height = (int)(((float)tex_height / (float)tex_width) * (float)thumbnail_width);
std::vector<uint8_t> png;
if (!ExtractFramebufferPixels(display_tex, display_flip, png, thumbnail_width, thumbnail_height)) {
if (!RenderFramebufferToPng(display_tex, display_flip, png,
2 * XEMU_SNAPSHOT_THUMBNAIL_WIDTH,
2 * XEMU_SNAPSHOT_THUMBNAIL_HEIGHT)) {
return NULL;
}
TextureBuffer *thumbnail = (TextureBuffer *)g_malloc(sizeof(TextureBuffer));
thumbnail->buffer = g_malloc(png.size() * sizeof(uint8_t));
thumbnail->channels = 3;
thumbnail->size = png.size() * sizeof(uint8_t);
memcpy(thumbnail->buffer, png.data(), thumbnail->size);

View File

@ -661,21 +661,60 @@ void RenderLogo(uint32_t time)
glUseProgram(0);
}
void RenderFramebuffer(GLint tex, int width, int height, bool flip, bool apply_scaling_factor)
// Scale <src> proportionally to fit in <max>
void ScaleDimensions(int src_width, int src_height, int max_width, int max_height, int *out_width, int *out_height)
{
float w_ratio = (float)max_width/(float)max_height;
float t_ratio = (float)src_width/(float)src_height;
if (w_ratio >= t_ratio) {
*out_width = (float)max_width * t_ratio/w_ratio;
*out_height = max_height;
} else {
*out_width = max_width;
*out_height = (float)max_height * w_ratio/t_ratio;
}
}
void RenderFramebuffer(GLint tex, int width, int height, bool flip, float scale[2])
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
DecalShader *s = g_framebuffer_shader;
s->flip = flip;
glViewport(0, 0, width, height);
glUseProgram(s->prog);
glBindVertexArray(s->vao);
glUniform1i(s->flipy_loc, s->flip);
glUniform4f(s->scale_offset_loc, scale[0], scale[1], 0, 0);
glUniform4f(s->tex_scale_offset_loc, 1.0, 1.0, 0, 0);
glUniform1i(s->tex_loc, 0);
const uint8_t *palette = nv2a_get_dac_palette();
for (int i = 0; i < 256; i++) {
uint32_t e = (palette[i * 3 + 2] << 16) | (palette[i * 3 + 1] << 8) |
palette[i * 3];
glUniform1ui(s->palette_loc[i], e);
}
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
}
void RenderFramebuffer(GLint tex, int width, int height, bool flip)
{
int tw, th;
float scale[2];
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tw);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th);
// Calculate scaling factors
float scale[2];
if (!apply_scaling_factor) {
scale[0] = 1.0;
scale[1] = 1.0;
} else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_STRETCH) {
if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_STRETCH) {
// Stretch to fit
scale[0] = 1.0;
scale[1] = 1.0;
@ -705,50 +744,27 @@ void RenderFramebuffer(GLint tex, int width, int height, bool flip, bool apply_s
}
}
DecalShader *s = g_framebuffer_shader;
s->flip = flip;
glViewport(0, 0, width, height);
glUseProgram(s->prog);
glBindVertexArray(s->vao);
glUniform1i(s->flipy_loc, s->flip);
glUniform4f(s->scale_offset_loc, scale[0], scale[1], 0, 0);
glUniform4f(s->tex_scale_offset_loc, 1.0, 1.0, 0, 0);
glUniform1i(s->tex_loc, 0);
const uint8_t *palette = nv2a_get_dac_palette();
for (int i = 0; i < 256; i++) {
uint32_t e = (palette[i * 3 + 2] << 16) | (palette[i * 3 + 1] << 8) |
palette[i * 3];
glUniform1ui(s->palette_loc[i], e);
}
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
RenderFramebuffer(tex, width, height, flip, scale);
}
bool ExtractFramebufferPixels(GLuint tex, bool flip, std::vector<uint8_t> &png, int width, int height)
bool RenderFramebufferToPng(GLuint tex, bool flip, std::vector<uint8_t> &png, int max_width, int max_height)
{
int width, height;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
assert((width == 0 && height == 0) || (width > 0 && height > 0));
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
bool params_from_tex = false;
if (width <= 0 && height <= 0) {
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
params_from_tex = true;
if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) {
width = height * (16.0f / 9.0f);
} else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) {
width = height * (4.0f / 3.0f);
}
glBindTexture(GL_TEXTURE_2D, 0);
assert(width > 0 && height > 0);
if (params_from_tex) {
if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) {
width = height * (16.0f / 9.0f);
} else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) {
width = height * (4.0f / 3.0f);
}
}
if (!max_width) max_width = width;
if (!max_height) max_height = height;
ScaleDimensions(width, height, max_width, max_height, &width, &height);
std::vector<uint8_t> pixels;
pixels.resize(width * height * 3);
@ -757,7 +773,8 @@ bool ExtractFramebufferPixels(GLuint tex, bool flip, std::vector<uint8_t> &png,
fbo.Target();
bool blend = glIsEnabled(GL_BLEND);
if (blend) glDisable(GL_BLEND);
RenderFramebuffer(tex, width, height, !flip, params_from_tex);
float scale[2] = {1.0, 1.0};
RenderFramebuffer(tex, width, height, !flip, scale);
if (blend) glEnable(GL_BLEND);
glPixelStorei(GL_PACK_ROW_LENGTH, width);
glPixelStorei(GL_PACK_IMAGE_HEIGHT, height);
@ -773,7 +790,8 @@ void SaveScreenshot(GLuint tex, bool flip)
Error *err = NULL;
char fname[128];
std::vector<uint8_t> png;
if (ExtractFramebufferPixels(tex, flip, png)) {
if (RenderFramebufferToPng(tex, flip, png)) {
time_t t = time(NULL);
struct tm *tmp = localtime(&t);
if (tmp) {

View File

@ -47,6 +47,8 @@ void RenderController(float frame_x, float frame_y, uint32_t primary_color,
uint32_t secondary_color, ControllerState *state);
void RenderControllerPort(float frame_x, float frame_y, int i,
uint32_t port_color);
void RenderFramebuffer(GLint tex, int width, int height, bool flip, bool apply_scaling_factor = true);
bool ExtractFramebufferPixels(GLuint tex, bool flip, std::vector<uint8_t> &png, int width = 0, int height = 0);
void RenderFramebuffer(GLint tex, int width, int height, bool flip);
void RenderFramebuffer(GLint tex, int width, int height, bool flip, float scale[2]);
bool RenderFramebufferToPng(GLuint tex, bool flip, std::vector<uint8_t> &png, int max_width = 0, int max_height = 0);
void SaveScreenshot(GLuint tex, bool flip);
void ScaleDimensions(int src_width, int src_height, int max_width, int max_height, int *out_width, int *out_height);

View File

@ -727,7 +727,7 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const
ImGui::PushFont(g_font_mgr.m_menu_font_medium);
ImVec2 ts_title = ImGui::CalcTextSize(snapshot->name);
ImVec2 thumbnail_size = g_viewport_mgr.Scale(ImVec2(XEMU_SNAPSHOT_WIDTH, XEMU_SNAPSHOT_HEIGHT));
ImVec2 thumbnail_size = g_viewport_mgr.Scale(ImVec2(XEMU_SNAPSHOT_THUMBNAIL_WIDTH, XEMU_SNAPSHOT_THUMBNAIL_HEIGHT));
ImVec2 thumbnail_pos(style.FramePadding.x, style.FramePadding.y);
ImVec2 name_pos(thumbnail_pos.x + thumbnail_size.x + style.FramePadding.x * 2, thumbnail_pos.y);
ImVec2 date_pos(name_pos.x, name_pos.y + ts_title.y + style.FramePadding.x);
@ -758,7 +758,27 @@ void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const
ImGui::SetCursorPosX(pos.x + thumbnail_pos.x);
ImGui::SetCursorPosY(pos.y + thumbnail_pos.y);
if (screenshot > 0) {
ImGui::Image((ImTextureID)(uint64_t)screenshot, thumbnail_size);
int thumbnail_width, thumbnail_height;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, screenshot);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &thumbnail_width);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &thumbnail_height);
// Draw black background behind thumbnail
draw_list->AddRectFilled(ImVec2(p0.x + thumbnail_pos.x, p0.y + thumbnail_pos.y),
ImVec2(p0.x + thumbnail_pos.x + thumbnail_size.x, p0.y + thumbnail_pos.y + thumbnail_size.y),
IM_COL32_BLACK);
// Center the thumbnail
int scaled_width, scaled_height;
ScaleDimensions(thumbnail_width, thumbnail_height, thumbnail_size.x, thumbnail_size.y, &scaled_width, &scaled_height);
ImVec2 img_pos = ImGui::GetCursorPos();
img_pos.x += (thumbnail_size.x - scaled_width) / 2;
img_pos.y += (thumbnail_size.y - scaled_height) / 2;
ImGui::SetCursorPos(img_pos);
ImGui::Image((ImTextureID)(uint64_t)screenshot, ImVec2(scaled_width, scaled_height));
} else {
ImGui::Image((ImTextureID)(uint64_t)g_icon_tex, thumbnail_size);
}