mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 00:57:44 -04:00
[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:
parent
4c3e23bcb9
commit
1602b05a2e
|
@ -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() {}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
/*!
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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]);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue