[graphics] some blit optimizations (#1667)

* [graphics] a few optimizations

* fullscreen crap

* working, other than aspect ratio thing

* same behavior as before

* fix blackout bug, add more error messages

* fix error when 0 size buffer

* rm warning

* one last 0 size issue
This commit is contained in:
water111 2022-07-17 14:45:00 -04:00 committed by GitHub
parent 4c3e23bcb9
commit 1602b05a2e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 424 additions and 234 deletions

View file

@ -26,14 +26,14 @@ class GfxDisplay {
int m_xpos;
int m_ypos;
GfxDisplayMode m_fullscreen_target_mode = GfxDisplayMode::Windowed;
GfxDisplayMode m_last_fullscreen_mode;
int m_fullscreen_screen;
int m_fullscreen_target_screen;
bool m_imgui_visible;
protected:
bool m_main;
GfxDisplayMode m_fullscreen_target_mode = GfxDisplayMode::Windowed;
GfxDisplayMode m_last_fullscreen_mode;
public:
virtual ~GfxDisplay() {}

View file

@ -16,41 +16,6 @@ struct LevelVis {
u8 data[2048];
};
struct FboState {
GLuint fbo = -1;
GLuint tex = -1;
GLuint fbo2 = -1;
GLuint tex2 = -1;
GLuint zbuf = -1;
GLenum render_targets[1] = {GL_COLOR_ATTACHMENT0};
int width = 640;
int height = 480;
int msaa = 4;
void delete_objects() {
if (fbo != -1) {
glDeleteFramebuffers(1, &fbo);
fbo = -1;
}
if (tex != -1) {
glDeleteTextures(1, &tex);
tex = -1;
}
if (fbo2 != -1) {
glDeleteFramebuffers(1, &fbo2);
fbo2 = -1;
}
if (tex2 != -1) {
glDeleteTextures(1, &tex2);
tex2 = -1;
}
if (zbuf != -1) {
glDeleteRenderbuffers(1, &zbuf);
zbuf = -1;
}
}
};
class EyeRenderer;
/*!
* The main renderer will contain a single SharedRenderState that's passed to all bucket renderers.
@ -92,12 +57,21 @@ struct SharedRenderState {
std::string load_status_debug;
int window_width_px;
int window_height_px;
int window_offset_x_px;
int window_offset_y_px;
// Information for renderers that need to read framebuffers:
// Most renderers can just use the framebuffer/glViewport set up by OpenGLRenderer, but special
// effects like sprite distort that read the framebuffer will need to know the details of the
// framebuffer setup.
FboState fbo_state;
// the framebuffer that bucket renderers should render to.
int render_fb_w = 0;
int render_fb_h = 0;
GLuint render_fb = -1;
// the region within that framebuffer to draw to.
int draw_region_w = 0;
int draw_region_h = 0;
int draw_offset_x = 0;
int draw_offset_y = 0;
};
/*!

View file

@ -392,9 +392,8 @@ void DirectRenderer::update_gl_blend() {
glBlendEquation(GL_FUNC_ADD);
} else if (state.a == GsAlpha::BlendMode::SOURCE && state.b == GsAlpha::BlendMode::SOURCE &&
state.c == GsAlpha::BlendMode::SOURCE && state.d == GsAlpha::BlendMode::SOURCE) {
// this is very weird...
glBlendFuncSeparate(GL_ONE, GL_ZERO, GL_ONE, GL_ZERO);
glBlendEquation(GL_FUNC_ADD);
// trick to disable alpha blending.
glDisable(GL_BLEND);
} else if (state.a == GsAlpha::BlendMode::SOURCE &&
state.b == GsAlpha::BlendMode::ZERO_OR_FIXED &&
state.c == GsAlpha::BlendMode::DEST && state.d == GsAlpha::BlendMode::DEST) {

View file

@ -303,6 +303,9 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) {
{
auto prof = m_profiler.root()->make_scoped_child("frame-setup");
setup_frame(settings);
if (settings.gpu_sync) {
glFinish();
}
}
{
@ -319,13 +322,16 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) {
// render the buckets!
{
auto prof = m_profiler.root()->make_scoped_child("buckets");
dispatch_buckets(dma, prof);
dispatch_buckets(dma, prof, settings.gpu_sync);
}
// apply effects done with PCRTC registers
{
auto prof = m_profiler.root()->make_scoped_child("pcrtc");
do_pcrtc_effects(settings.pmode_alp_register, &m_render_state, prof);
if (settings.gpu_sync) {
glFinish();
}
}
if (settings.draw_render_debug_window) {
@ -333,6 +339,9 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) {
draw_renderer_selection_window();
// add a profile bar for the imgui stuff
vif_interrupt_callback();
if (settings.gpu_sync) {
glFinish();
}
}
m_last_pmode_alp = settings.pmode_alp_register;
@ -362,8 +371,19 @@ void OpenGLRenderer::render(DmaFollower dma, const RenderOptions& settings) {
}
if (settings.save_screenshot) {
finish_screenshot(settings.screenshot_path, m_render_state.fbo_state.width,
m_render_state.fbo_state.height, 0, 0, m_render_state.fbo_state.fbo2);
Fbo* screenshot_src;
// can't screenshot from a multisampled buffer directly -
if (m_fbo_state.resources.resolve_buffer.valid) {
screenshot_src = &m_fbo_state.resources.resolve_buffer;
} else {
screenshot_src = m_fbo_state.render_fbo;
}
finish_screenshot(settings.screenshot_path, screenshot_src->width, screenshot_src->height, 0, 0,
screenshot_src->fbo_id);
}
if (settings.gpu_sync) {
glFinish();
}
}
@ -399,113 +419,214 @@ void OpenGLRenderer::draw_renderer_selection_window() {
ImGui::End();
}
namespace {
Fbo make_fbo(int w, int h, int msaa, bool make_zbuf_and_stencil) {
Fbo result;
bool use_multisample = msaa > 1;
// make framebuffer object
glGenFramebuffers(1, &result.fbo_id);
glBindFramebuffer(GL_FRAMEBUFFER, result.fbo_id);
result.valid = true;
// make texture that will hold the colors of the framebuffer
GLuint tex;
glGenTextures(1, &tex);
result.tex_id = tex;
glActiveTexture(GL_TEXTURE0);
if (use_multisample) {
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, msaa, GL_RGBA8, w, h, GL_TRUE);
} else {
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
}
// make depth and stencil buffers that will hold the... depth and stencil buffers
if (make_zbuf_and_stencil) {
GLuint zbuf;
glGenRenderbuffers(1, &zbuf);
result.zbuf_stencil_id = zbuf;
glBindRenderbuffer(GL_RENDERBUFFER, zbuf);
if (use_multisample) {
glRenderbufferStorageMultisample(GL_RENDERBUFFER, msaa, GL_DEPTH24_STENCIL8, w, h);
} else {
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, w, h);
}
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, zbuf);
}
// attach texture to framebuffer as target for colors
if (use_multisample) {
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0);
} else {
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
}
GLenum render_targets[1] = {GL_COLOR_ATTACHMENT0};
glDrawBuffers(1, render_targets);
auto status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE) {
lg::error("Failed to setup framebuffer: {} {} {} {}\n", w, h, msaa, make_zbuf_and_stencil);
switch (status) {
case GL_FRAMEBUFFER_UNDEFINED:
printf("GL_FRAMEBUFFER_UNDEFINED\n");
break;
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
printf("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT\n");
break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
printf("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT\n");
break;
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
printf("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER\n");
break;
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
printf("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER\n");
break;
case GL_FRAMEBUFFER_UNSUPPORTED:
printf("GL_FRAMEBUFFER_UNSUPPORTED\n");
break;
case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
printf("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE\n");
break;
case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
printf("GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS\n");
break;
}
ASSERT(false);
}
result.multisample_count = msaa;
result.multisampled = use_multisample;
result.is_window = false;
result.width = w;
result.height = h;
return result;
}
} // namespace
/*!
* Pre-render frame setup.
*/
void OpenGLRenderer::setup_frame(const RenderOptions& settings) {
auto& fbo_state = m_render_state.fbo_state;
// glfw controls the window framebuffer, so we just update the size:
auto& window_fb = m_fbo_state.resources.window;
bool window_resized = window_fb.width != settings.window_framebuffer_width ||
window_fb.height != settings.window_framebuffer_height;
window_fb.valid = true;
window_fb.is_window = true;
window_fb.fbo_id = 0;
window_fb.width = settings.window_framebuffer_width;
window_fb.height = settings.window_framebuffer_height;
window_fb.multisample_count = 1;
window_fb.multisampled = false;
// restart the buffers if settings changed.
if (settings.game_res_w != fbo_state.width || settings.game_res_h != fbo_state.height ||
settings.msaa_samples != fbo_state.msaa) {
fbo_state.delete_objects();
}
// see if the render FBO is still applicable
if (!m_fbo_state.render_fbo || window_resized ||
!m_fbo_state.render_fbo->matches(settings.game_res_w, settings.game_res_h,
settings.msaa_samples)) {
// doesn't match, set up a new one for these settings
lg::info("FBO Setup: requested {}x{}, msaa {}", settings.game_res_w, settings.game_res_h,
settings.msaa_samples);
if (fbo_state.fbo == -1 || fbo_state.fbo2 == -1 || fbo_state.tex == -1 || fbo_state.tex2 == -1 ||
fbo_state.zbuf == -1) {
fbo_state.width = settings.game_res_w;
fbo_state.height = settings.game_res_h;
fbo_state.msaa = settings.msaa_samples;
// clear old framebuffers
m_fbo_state.resources.render_buffer.clear();
m_fbo_state.resources.resolve_buffer.clear();
bool bad = false;
// make framebuffer object
if (fbo_state.fbo == -1) {
glGenFramebuffers(1, &fbo_state.fbo);
}
glBindFramebuffer(GL_FRAMEBUFFER, fbo_state.fbo);
// make texture that will hold the colors of the framebuffer
if (fbo_state.tex == -1) {
glGenTextures(1, &fbo_state.tex);
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, fbo_state.tex);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, fbo_state.msaa, GL_RGBA8, fbo_state.width,
fbo_state.height, GL_TRUE);
// make depth and stencil buffers that will hold the... depth and stencil buffers
if (fbo_state.zbuf == -1) {
glGenRenderbuffers(1, &fbo_state.zbuf);
}
glBindRenderbuffer(GL_RENDERBUFFER, fbo_state.zbuf);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, fbo_state.msaa, GL_DEPTH24_STENCIL8,
fbo_state.width, fbo_state.height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
fbo_state.zbuf);
// attach texture to framebuffer as target for colors
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE,
fbo_state.tex, 0);
glDrawBuffers(1, fbo_state.render_targets);
bad |= glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE;
// make framebuffer object
if (fbo_state.fbo2 == -1) {
glGenFramebuffers(1, &fbo_state.fbo2);
}
glBindFramebuffer(GL_FRAMEBUFFER, fbo_state.fbo2);
// make texture that will hold the msaa resolved color buffer
if (fbo_state.tex2 == -1) {
glGenTextures(1, &fbo_state.tex2);
}
glBindTexture(GL_TEXTURE_2D, fbo_state.tex2);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, fbo_state.width, fbo_state.height, 0, GL_RGBA,
GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// attach texture to framebuffer as target for colors
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo_state.tex2, 0);
glDrawBuffers(1, fbo_state.render_targets);
bad |= glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE;
if (!bad) {
glBindFramebuffer(GL_FRAMEBUFFER, fbo_state.fbo);
// first, see if we can just render straight to the display framebuffer.
if (window_fb.matches(settings.game_res_w, settings.game_res_h, settings.msaa_samples)) {
// it matches - no need for extra framebuffers.
lg::info("FBO Setup: rendering directly to window framebuffer");
m_fbo_state.render_fbo = &m_fbo_state.resources.window;
} else {
lg::error("bad framebuffer setup. fbo: {}, tex: {}, zbuf: {}, fbo2: {}, tex2: {}",
fbo_state.fbo, fbo_state.tex, fbo_state.zbuf, fbo_state.fbo2, fbo_state.tex2);
lg::error("size was: {} x {}\n", fbo_state.width, fbo_state.height);
fbo_state.delete_objects();
lg::info("FBO Setup: window didn't match: {} {}", window_fb.width, window_fb.height);
// create a fbo to render to, with the desired settings
m_fbo_state.resources.render_buffer =
make_fbo(settings.game_res_w, settings.game_res_h, settings.msaa_samples, true);
m_fbo_state.render_fbo = &m_fbo_state.resources.render_buffer;
bool resolution_matches =
window_fb.width == settings.game_res_w && window_fb.height == settings.game_res_h;
bool msaa_matches = window_fb.multisample_count == settings.msaa_samples;
if (!resolution_matches && !msaa_matches) {
lg::info("FBO Setup: using second temporary buffer: res: {}x{} {}x{}", window_fb.width,
window_fb.height, settings.game_res_w, settings.game_res_h);
// we'll need a temporary fbo to do the msaa resolve step
// non-multisampled, and doesn't need z/stencil
m_fbo_state.resources.resolve_buffer =
make_fbo(settings.game_res_w, settings.game_res_h, 1, false);
} else {
lg::info("FBO Setup: not using second temporary buffer");
}
}
} else {
// we have the objects. bind framebuffer.
glBindFramebuffer(GL_FRAMEBUFFER, fbo_state.fbo);
}
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo_state.render_fbo->fbo_id);
ASSERT_MSG(settings.game_res_w > 0 && settings.game_res_h > 0,
fmt::format("Bad viewport size from game_res: {}x{}\n", settings.game_res_w,
settings.game_res_h));
glViewport(0, 0, settings.game_res_w, settings.game_res_h);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClearDepth(0.0);
glDepthMask(GL_TRUE);
// Note: could rely on sky renderer to clear depth and color, but this causes problems with
// letterboxing
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glDisable(GL_BLEND);
m_render_state.window_width_px = settings.window_width_px;
m_render_state.window_height_px = settings.window_height_px;
m_render_state.window_offset_x_px = settings.lbox_width_px;
m_render_state.window_offset_y_px = settings.lbox_height_px;
m_render_state.render_fb_w = settings.game_res_w;
m_render_state.render_fb_h = settings.game_res_h;
// compare the aspect ratio of the frame buffer to the requested draw area.
float fb_aspect =
(float)settings.window_framebuffer_width / (float)settings.window_framebuffer_height;
float draw_aspect = (float)settings.draw_region_width / (float)settings.draw_region_height;
float squash = fb_aspect / draw_aspect;
// start with the full area
m_render_state.draw_region_w = m_render_state.render_fb_w;
m_render_state.draw_region_h = m_render_state.render_fb_h;
// and letterbox as needed
if (squash > 1) {
m_render_state.draw_region_w = ((float)m_render_state.draw_region_w / squash) + 0.1;
} else {
m_render_state.draw_region_h = ((float)m_render_state.draw_region_h * squash) + 0.1;
}
// center the letterbox
m_render_state.draw_offset_x = (settings.game_res_w - m_render_state.draw_region_w) / 2;
m_render_state.draw_offset_y = (settings.game_res_h - m_render_state.draw_region_h) / 2;
if (settings.borderless_windows_hacks) {
// pretend the framebuffer is 1 pixel shorter on borderless. fullscreen issues!
// add one pixel of vertical letterbox on borderless to make up for extra line
m_render_state.draw_region_h--;
m_render_state.draw_offset_y++;
}
m_render_state.render_fb = m_fbo_state.render_fbo->fbo_id;
if (m_render_state.draw_region_w <= 0 || m_render_state.draw_region_h <= 0) {
// trying to draw to 0 size region... opengl doesn't like this.
m_render_state.draw_region_w = 640;
m_render_state.draw_region_h = 480;
}
glViewport(m_render_state.draw_offset_x, m_render_state.draw_offset_y,
m_render_state.draw_region_w, m_render_state.draw_region_h);
}
/*!
* This function finds buckets and dispatches them to the appropriate part.
*/
void OpenGLRenderer::dispatch_buckets(DmaFollower dma, ScopedProfilerNode& prof) {
void OpenGLRenderer::dispatch_buckets(DmaFollower dma,
ScopedProfilerNode& prof,
bool sync_after_buckets) {
// The first thing the DMA chain should be a call to a common default-registers chain.
// this chain resets the state of the GS. After this is buckets
m_category_times.fill(0);
@ -543,6 +664,11 @@ void OpenGLRenderer::dispatch_buckets(DmaFollower dma, ScopedProfilerNode& prof)
g_current_render = renderer->name_and_id();
// lg::info("Render: {} start", g_current_render);
renderer->render(dma, &m_render_state, bucket_prof);
if (sync_after_buckets) {
auto pp = scoped_prof("finish");
glFinish();
}
// lg::info("Render: {} end", g_current_render);
// should have ended at the start of the next chain
ASSERT(dma.current_tag_offset() == m_render_state.next_bucket);
@ -596,45 +722,54 @@ void OpenGLRenderer::finish_screenshot(const std::string& output_name,
void OpenGLRenderer::do_pcrtc_effects(float alp,
SharedRenderState* render_state,
ScopedProfilerNode& prof) {
int w = render_state->fbo_state.width;
int h = render_state->fbo_state.height;
if (m_fbo_state.render_fbo->is_window) {
// nothing to do!
} else {
Fbo* window_blit_src = nullptr;
const Fbo& window_fbo = m_fbo_state.resources.window;
if (m_fbo_state.resources.resolve_buffer.valid) {
glBindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo_state.render_fbo->fbo_id);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo_state.resources.resolve_buffer.fbo_id);
glBlitFramebuffer(0, // srcX0
0, // srcY0
m_fbo_state.render_fbo->width, // srcX1
m_fbo_state.render_fbo->height, // srcY1
0, // dstX0
0, // dstY0
m_fbo_state.resources.resolve_buffer.width, // dstX1
m_fbo_state.resources.resolve_buffer.height, // dstY1
GL_COLOR_BUFFER_BIT, // mask
GL_LINEAR // filter
);
window_blit_src = &m_fbo_state.resources.resolve_buffer;
} else {
window_blit_src = &m_fbo_state.resources.render_buffer;
}
// msaa "resolve" - this blit goes from a multisampled framebuffer (fbo/tex) to a normal one
// (fbo2/tex2)
glBindFramebuffer(GL_READ_FRAMEBUFFER, render_state->fbo_state.fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, render_state->fbo_state.fbo2);
glBlitFramebuffer(0, // srcX0
0, // srcY0
w, // srcX1
h, // srcY1
0, // dstX0
0, // dstY0
w, // dstX1
h, // dstY1
GL_COLOR_BUFFER_BIT, // mask
GL_LINEAR // filter
);
glBindFramebuffer(GL_READ_FRAMEBUFFER, window_blit_src->fbo_id);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, // srcX0
0, // srcY0
window_blit_src->width, // srcX1
window_blit_src->height, // srcY1
0, // dstX0
0, // dstY0
window_fbo.width, // dstX1
window_fbo.height, // dstY1
GL_COLOR_BUFFER_BIT, // mask
GL_LINEAR // filter
);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
if (alp < 1) {
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
glBlendEquation(GL_FUNC_ADD);
glViewport(0, 0, m_fbo_state.resources.window.width, m_fbo_state.resources.window.height);
// Render the resolved texture to the screen directly now
// TODO: if fbo and the screen have the same resolution, it might be possible to
// glBlitFrameBuffer to the screen directly and skip fbo2/tex2.
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(render_state->window_offset_x_px, render_state->window_offset_y_px,
render_state->window_width_px, render_state->window_height_px);
m_blackout_renderer.draw(Vector4f(0, 0, 0, 1.f - alp), render_state, prof);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClearDepth(0.0);
glDepthMask(GL_TRUE);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
glBlendEquation(GL_FUNC_ADD);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, render_state->fbo_state.tex2);
m_blackout_renderer.draw(Vector4f(0, 0, 0, alp), render_state, prof);
glEnable(GL_DEPTH_TEST);
glEnable(GL_DEPTH_TEST);
}
}

View file

@ -13,23 +13,80 @@
#include "game/tools/subtitles/subtitle_editor.h"
struct RenderOptions {
int window_height_px = 0;
int window_width_px = 0;
int lbox_height_px = 0;
int lbox_width_px = 0;
bool draw_render_debug_window = false;
bool draw_profiler_window = false;
bool draw_small_profiler_window = false;
bool draw_subtitle_editor_window = false;
// internal rendering settings - The OpenGLRenderer will internally use this resolution/format.
int msaa_samples = 4;
int game_res_w = 640;
int game_res_h = 480;
// size of the window's framebuffer (framebuffer 0)
// The renderer needs to know this to do an optimization to render directly to the window's
// framebuffer when possible.
int window_framebuffer_height = 0;
int window_framebuffer_width = 0;
// the part of the window that we should draw to. The rest is black. This value is determined by
// logic inside of the game - it needs to know the desired aspect ratio.
int draw_region_height = 0;
int draw_region_width = 0;
// windows-specific tweaks to the size of the drawing area in borderless.
bool borderless_windows_hacks = false;
bool save_screenshot = false;
std::string screenshot_path;
float pmode_alp_register = 0.f;
// when enabled, does a `glFinish()` after each major rendering pass. This blocks until the GPU
// is done working, making it easier to profile GPU utilization.
bool gpu_sync = false;
};
struct Fbo {
bool valid = false; // do we have an OpenGL fbo_id?
GLuint fbo_id = -1;
// optional rgba/zbuffer/stencil data.
std::optional<GLuint> tex_id;
std::optional<GLuint> zbuf_stencil_id;
bool multisampled = false;
int multisample_count = 0; // Should be 1 if multisampled is disabled
bool is_window = false;
int width = 640;
int height = 480;
// Does this fbo match the given format? MSAA = 1 will accept a normal buffer, or a multisample 1x
bool matches(int w, int h, int msaa) const {
int effective_msaa = multisampled ? multisample_count : 1;
return valid && width == w && height == h && effective_msaa == msaa;
}
// Free opengl resources, if we have any.
void clear() {
if (valid) {
glDeleteFramebuffers(1, &fbo_id);
fbo_id = -1;
if (tex_id) {
glDeleteTextures(1, &tex_id.value());
tex_id.reset();
}
if (zbuf_stencil_id) {
glDeleteRenderbuffers(1, &zbuf_stencil_id.value());
zbuf_stencil_id.reset();
}
valid = false;
}
}
};
/*!
@ -49,7 +106,7 @@ class OpenGLRenderer {
private:
void setup_frame(const RenderOptions& settings);
void dispatch_buckets(DmaFollower dma, ScopedProfilerNode& prof);
void dispatch_buckets(DmaFollower dma, ScopedProfilerNode& prof, bool sync_after_buckets);
void do_pcrtc_effects(float alp, SharedRenderState* render_state, ScopedProfilerNode& prof);
void init_bucket_renderers();
void draw_renderer_selection_window();
@ -80,4 +137,14 @@ class OpenGLRenderer {
float m_last_pmode_alp = 1.;
bool m_enable_fast_blackout_loads = true;
struct FboState {
struct {
Fbo window; // provided by glfw
Fbo render_buffer; // temporary buffer to render to
Fbo resolve_buffer; // temporary buffer to resolve to
} resources;
Fbo* render_fbo = nullptr; // the selected fbo from the three above to use for rendering
} m_fbo_state;
};

View file

@ -711,22 +711,22 @@ void Sprite3::distort_draw_instanced(SharedRenderState* render_state, ScopedProf
void Sprite3::distort_draw_common(SharedRenderState* render_state, ScopedProfilerNode& /*prof*/) {
// The distort effect needs to read the current framebuffer, so copy what's been rendered so far
// to a texture that we can then pass to the shader
glBindFramebuffer(GL_READ_FRAMEBUFFER, render_state->fbo_state.fbo);
glBindFramebuffer(GL_READ_FRAMEBUFFER, render_state->render_fb);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_distort_ogl.fbo);
glBlitFramebuffer(0, // srcX0
0, // srcY0
render_state->fbo_state.width, // srcX1
render_state->fbo_state.height, // srcY1
0, // dstX0
0, // dstY0
m_distort_ogl.fbo_width, // dstX1
m_distort_ogl.fbo_height, // dstY1
GL_COLOR_BUFFER_BIT, // mask
GL_NEAREST // filter
glBlitFramebuffer(render_state->draw_offset_x, // srcX0
render_state->draw_offset_y, // srcY0
render_state->draw_offset_x + render_state->draw_region_w, // srcX1
render_state->draw_offset_y + render_state->draw_region_h, // srcY1
0, // dstX0
0, // dstY0
m_distort_ogl.fbo_width, // dstX1
m_distort_ogl.fbo_height, // dstY1
GL_COLOR_BUFFER_BIT, // mask
GL_NEAREST // filter
);
glBindFramebuffer(GL_FRAMEBUFFER, render_state->fbo_state.fbo);
glBindFramebuffer(GL_FRAMEBUFFER, render_state->render_fb);
// Set up OpenGL state
m_current_mode.set_depth_write_enable(!m_sprite_distorter_setup.zbuf.zmsk()); // zbuf
@ -741,10 +741,10 @@ void Sprite3::distort_draw_common(SharedRenderState* render_state, ScopedProfile
void Sprite3::distort_setup_framebuffer_dims(SharedRenderState* render_state) {
// Distort framebuffer must be the same dimensions as the default window framebuffer
if (m_distort_ogl.fbo_width != render_state->fbo_state.width ||
m_distort_ogl.fbo_height != render_state->fbo_state.height) {
m_distort_ogl.fbo_width = render_state->fbo_state.width;
m_distort_ogl.fbo_height = render_state->fbo_state.height;
if (m_distort_ogl.fbo_width != render_state->draw_region_w ||
m_distort_ogl.fbo_height != render_state->draw_region_h) {
m_distort_ogl.fbo_width = render_state->draw_region_w;
m_distort_ogl.fbo_height = render_state->draw_region_h;
glBindTexture(GL_TEXTURE_2D, m_distort_ogl.fbo_texture);

View file

@ -140,7 +140,7 @@ void FullScreenDraw::draw(const math::Vector4f& color,
ScopedProfilerNode& prof) {
glBindVertexArray(m_vao);
glBindBuffer(GL_ARRAY_BUFFER, m_vertex_buffer);
auto& shader = render_state->shaders[ShaderId::POST_PROCESSING];
auto& shader = render_state->shaders[ShaderId::SOLID_COLOR];
shader.activate();
glUniform4f(glGetUniformLocation(shader.id(), "fragment_color"), color[0], color[1], color[2],
color[3]);

View file

@ -259,11 +259,12 @@ static bool endsWith(std::string_view str, std::string_view suffix) {
void render_game_frame(int game_width,
int game_height,
int window_width,
int window_height,
int lbox_width,
int lbox_height,
int msaa_samples) {
int window_fb_width,
int window_fb_height,
int draw_region_width,
int draw_region_height,
int msaa_samples,
bool windows_borderless_hack) {
// wait for a copied chain.
bool got_chain = false;
{
@ -280,19 +281,23 @@ void render_game_frame(int game_width,
RenderOptions options;
options.game_res_w = game_width;
options.game_res_h = game_height;
options.window_height_px = window_height;
options.window_width_px = window_width;
options.lbox_height_px = lbox_height;
options.lbox_width_px = lbox_width;
options.window_framebuffer_width = window_fb_width;
options.window_framebuffer_height = window_fb_height;
options.draw_region_height = draw_region_height;
options.draw_region_width = draw_region_width;
options.msaa_samples = msaa_samples;
options.draw_render_debug_window = g_gfx_data->debug_gui.should_draw_render_debug();
options.draw_profiler_window = g_gfx_data->debug_gui.should_draw_profiler();
options.draw_subtitle_editor_window = g_gfx_data->debug_gui.should_draw_subtitle_editor();
options.save_screenshot = false;
options.gpu_sync = g_gfx_data->debug_gui.should_gl_finish();
options.borderless_windows_hacks = windows_borderless_hack;
if (g_gfx_data->debug_gui.get_screenshot_flag()) {
options.save_screenshot = true;
options.game_res_w = g_gfx_data->debug_gui.screenshot_width;
options.game_res_h = g_gfx_data->debug_gui.screenshot_height;
options.draw_region_width = options.game_res_w;
options.draw_region_height = options.game_res_h;
options.msaa_samples = g_gfx_data->debug_gui.screenshot_samples;
}
options.draw_small_profiler_window = g_gfx_data->debug_gui.small_profiler;
@ -487,38 +492,30 @@ void GLDisplay::render() {
ImGui::NewFrame();
}
// window size
int win_w = Gfx::g_global_settings.lbox_w;
int win_h = Gfx::g_global_settings.lbox_h;
// framebuffer size
int fbuf_w, fbuf_h;
glfwGetFramebufferSize(m_window, &fbuf_w, &fbuf_h);
bool windows_borderless_hacks = false;
#ifdef _WIN32
if (last_fullscreen_mode() == GfxDisplayMode::Borderless) {
// pretend the framebuffer is 1 pixel shorter on borderless. fullscreen issues!
fbuf_h--;
}
#endif
// horizontal letterbox size
int lbox_w = (fbuf_w - win_w) / 2;
// vertical letterbox size
int lbox_h = (fbuf_h - win_h) / 2;
#ifdef _WIN32
if (last_fullscreen_mode() == GfxDisplayMode::Borderless) {
// add one pixel of vertical letterbox on borderless to make up for extra line
lbox_h++;
windows_borderless_hacks = true;
}
#endif
// render game!
if (g_gfx_data->debug_gui.should_advance_frame()) {
auto p = scoped_prof("game-render");
render_game_frame(Gfx::g_global_settings.game_res_w, Gfx::g_global_settings.game_res_h, win_w,
win_h, lbox_w, lbox_h, Gfx::g_global_settings.msaa_samples);
}
if (g_gfx_data->debug_gui.should_gl_finish()) {
auto p = scoped_prof("gl-finish");
glFinish();
int game_res_w = Gfx::g_global_settings.game_res_w;
int game_res_h = Gfx::g_global_settings.game_res_h;
if (game_res_w <= 0 || game_res_h <= 0) {
// if the window size reports 0, the game will ask for a 0 sized window, and nothing likes
// that.
game_res_w = 640;
game_res_h = 480;
}
render_game_frame(game_res_w, game_res_h, fbuf_w, fbuf_h, Gfx::g_global_settings.lbox_w,
Gfx::g_global_settings.lbox_h, Gfx::g_global_settings.msaa_samples,
windows_borderless_hacks);
}
// render debug
@ -550,33 +547,51 @@ void GLDisplay::render() {
Gfx::g_global_settings.target_fps, Gfx::g_global_settings.experimental_accurate_lag,
Gfx::g_global_settings.sleep_in_frame_limiter, g_gfx_data->last_engine_time);
}
// actually wait for vsync
if (g_gfx_data->debug_gui.should_gl_finish()) {
glFinish();
}
// Start timing for the next frame.
g_gfx_data->debug_gui.start_frame();
prof().instant_event("ROOT");
update_global_profiler();
if (!minimized() && fullscreen_pending()) {
fullscreen_flush();
}
update_last_fullscreen_mode();
// toggle even odd and wake up engine waiting on vsync.
// TODO: we could play with moving this earlier, right after the final bucket renderer.
// it breaks the VIF-interrupt profiling though.
{
prof().instant_event("engine-notify");
std::unique_lock<std::mutex> lock(g_gfx_data->sync_mutex);
g_gfx_data->frame_idx++;
g_gfx_data->sync_cv.notify_all();
}
{
auto p = scoped_prof("fullscreen-update");
// slow, takes ~0.15 ms on linux
auto current_fullscreen_mode = get_fullscreen();
// checking minimized also takes ~0.1 ms, only check if we need to update fullscreen modes
if (current_fullscreen_mode != m_fullscreen_target_mode && !minimized()) {
fullscreen_flush();
}
m_last_fullscreen_mode = current_fullscreen_mode;
}
// reboot whole game, if requested
if (g_gfx_data->debug_gui.want_reboot_in_debug) {
g_gfx_data->debug_gui.want_reboot_in_debug = false;
MasterExit = RuntimeExitStatus::RESTART_IN_DEBUG;
}
// exit if display window was closed
if (glfwWindowShouldClose(m_window)) {
std::unique_lock<std::mutex> lock(g_gfx_data->sync_mutex);
MasterExit = RuntimeExitStatus::EXIT;
g_gfx_data->sync_cv.notify_all();
{
auto p = scoped_prof("check-close-window");
// exit if display window was closed
if (glfwWindowShouldClose(m_window)) {
std::unique_lock<std::mutex> lock(g_gfx_data->sync_mutex);
MasterExit = RuntimeExitStatus::EXIT;
g_gfx_data->sync_cv.notify_all();
}
}
}