From c802a6a7ed499c5f4829034033e4f1327c6ec6bd Mon Sep 17 00:00:00 2001 From: water Date: Thu, 3 Mar 2022 21:44:10 -0500 Subject: [PATCH] do eye rendering on the GPU --- game/CMakeLists.txt | 1 + game/graphics/opengl_renderer/EyeRenderer.cpp | 563 ++++++++++++------ game/graphics/opengl_renderer/EyeRenderer.h | 72 ++- .../opengl_renderer/OpenGLRenderer.cpp | 1 + game/graphics/opengl_renderer/Shader.cpp | 1 + game/graphics/opengl_renderer/Shader.h | 1 + game/graphics/opengl_renderer/SkyBlendCPU.cpp | 21 +- game/graphics/opengl_renderer/SkyBlendCPU.h | 7 +- game/graphics/opengl_renderer/SkyBlendGPU.cpp | 6 +- game/graphics/opengl_renderer/SkyBlendGPU.h | 5 + .../opengl_renderer/TextureUploadHandler.cpp | 2 +- .../graphics/opengl_renderer/opengl_utils.cpp | 57 ++ game/graphics/opengl_renderer/opengl_utils.h | 39 ++ .../graphics/opengl_renderer/shaders/eye.frag | 10 + .../graphics/opengl_renderer/shaders/eye.vert | 9 + 15 files changed, 583 insertions(+), 212 deletions(-) create mode 100644 game/graphics/opengl_renderer/opengl_utils.cpp create mode 100644 game/graphics/opengl_renderer/opengl_utils.h create mode 100644 game/graphics/opengl_renderer/shaders/eye.frag create mode 100644 game/graphics/opengl_renderer/shaders/eye.vert diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 491c68d589..d153db8f38 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -85,6 +85,7 @@ set(RUNTIME_SOURCE graphics/opengl_renderer/Loader.cpp graphics/opengl_renderer/MercProgram.cpp graphics/opengl_renderer/MercRenderer.cpp + graphics/opengl_renderer/opengl_utils.cpp graphics/opengl_renderer/OpenGLRenderer.cpp graphics/opengl_renderer/Profiler.cpp graphics/opengl_renderer/Shader.cpp diff --git a/game/graphics/opengl_renderer/EyeRenderer.cpp b/game/graphics/opengl_renderer/EyeRenderer.cpp index 96c46b316b..4144d5107f 100644 --- a/game/graphics/opengl_renderer/EyeRenderer.cpp +++ b/game/graphics/opengl_renderer/EyeRenderer.cpp @@ -4,24 +4,68 @@ #include "common/util/FileUtil.h" #include "third-party/imgui/imgui.h" +///////////////////////// +// Bucket Renderer +///////////////////////// EyeRenderer::EyeRenderer(const std::string& name, BucketId id) : BucketRenderer(name, id) {} void EyeRenderer::init_textures(TexturePool& texture_pool) { + // set up eyes for (int pair_idx = 0; pair_idx < NUM_EYE_PAIRS; pair_idx++) { for (int lr = 0; lr < 2; lr++) { - GLuint gl_tex; - glGenTextures(1, &gl_tex); - u32 tbp = EYE_BASE_BLOCK + pair_idx * 2 + lr; - TextureInput in; - in.gpu_texture = gl_tex; - in.w = 32; - in.h = 32; - in.page_name = "PC-EYES"; - in.name = fmt::format("{}-eye-{}", lr ? "left" : "right", pair_idx); - auto* gpu_tex = texture_pool.give_texture_and_load_to_vram(in, tbp); - m_eye_textures[pair_idx * 2 + lr] = {gl_tex, gpu_tex, tbp}; + u32 tidx = pair_idx * 2 + lr; + + // CPU + { + GLuint gl_tex; + glGenTextures(1, &gl_tex); + u32 tbp = EYE_BASE_BLOCK + pair_idx * 2 + lr; + TextureInput in; + in.gpu_texture = gl_tex; + in.w = 32; + in.h = 32; + in.page_name = "PC-EYES"; + in.name = fmt::format("{}-eye-cpu-{}", lr ? "left" : "right", pair_idx); + auto* gpu_tex = texture_pool.give_texture_and_load_to_vram(in, tbp); + m_cpu_eye_textures[tidx] = {gl_tex, gpu_tex, tbp}; + } + + // GPU + { + u32 tbp = EYE_BASE_BLOCK + pair_idx * 2 + lr; + TextureInput in; + in.gpu_texture = m_gpu_eye_textures[tidx].fb.texture(); + in.w = 32; + in.h = 32; + in.page_name = "PC-EYES"; + in.name = fmt::format("{}-eye-gpu-{}", lr ? "left" : "right", pair_idx); + m_gpu_eye_textures[tidx].gpu_tex = texture_pool.give_texture_and_load_to_vram(in, tbp); + m_gpu_eye_textures[tidx].tbp = tbp; + } } } + + // set up vertices for GPU mode + glGenVertexArrays(1, &m_vao); + glBindVertexArray(m_vao); + glGenBuffers(1, &m_gl_vertex_buffer); + glBindBuffer(GL_ARRAY_BUFFER, m_gl_vertex_buffer); + glBufferData(GL_ARRAY_BUFFER, VTX_BUFFER_FLOATS * sizeof(float), nullptr, GL_STREAM_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, // location 0 in the shader + 4, // 2 floats per vert + GL_FLOAT, // floats + GL_TRUE, // normalized, ignored, + sizeof(float) * 4, // + (void*)0 // offset in array + ); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); +} + +EyeRenderer::~EyeRenderer() { + glDeleteVertexArrays(1, &m_vao); + glDeleteBuffers(1, &m_gl_vertex_buffer); } void EyeRenderer::render(DmaFollower& dma, @@ -53,7 +97,7 @@ void EyeRenderer::render(DmaFollower& dma, return; } - handle_eye_dma2(dma, render_state, prof); + handle_eye_dma2(dma, render_state, prof); while (dma.current_tag_offset() != render_state->next_bucket) { auto data = dma.read_and_advance(); @@ -61,13 +105,21 @@ void EyeRenderer::render(DmaFollower& dma, } } -struct ScissorInfo { - int x0, x1; - int y0, y1; - std::string print() const { return fmt::format("x : [{}, {}], y : [{}, {}]", x0, x1, y0, y1); } -}; +void EyeRenderer::draw_debug_window() { + ImGui::Checkbox("Use GPU", &m_use_gpu); + ImGui::Text("Time: %.3f ms\n", m_average_time_ms); + ImGui::Text("Debug:\n%s", m_debug.c_str()); + if (!m_use_gpu) { + ImGui::Checkbox("bilinear", &m_use_bilinear); + } + ImGui::Checkbox("alpha hack", &m_alpha_hack); +} + +////////////////////// +// DMA Decode +////////////////////// -ScissorInfo decode_scissor(const DmaTransfer& dma) { +EyeRenderer::ScissorInfo decode_scissor(const DmaTransfer& dma) { ASSERT(dma.vif0() == 0); ASSERT(dma.vifcode1().kind == VifCode::Kind::DIRECT); ASSERT(dma.size_bytes == 32); @@ -82,7 +134,7 @@ ScissorInfo decode_scissor(const DmaTransfer& dma) { u8 reg_addr; memcpy(®_addr, dma.data + 24, 1); ASSERT((GsRegisterAddress)reg_addr == GsRegisterAddress::SCISSOR_1); - ScissorInfo result; + EyeRenderer::ScissorInfo result; u64 val; memcpy(&val, dma.data + 16, 8); GsScissor reg(val); @@ -93,23 +145,7 @@ ScissorInfo decode_scissor(const DmaTransfer& dma) { return result; } -struct SpriteInfo { - u8 a; - u32 uv0[2]; - u32 uv1[2]; - u32 xyz0[3]; - u32 xyz1[3]; - - std::string print() const { - std::string result; - result += - fmt::format("a: {:x} uv: ({}, {}), ({}, {}) xyz: ({}, {}, {}), ({}, {}, {})", a, uv0[0], - uv0[1], uv1[0], uv1[1], xyz0[0], xyz0[1], xyz0[2], xyz1[0], xyz1[1], xyz1[2]); - return result; - } -}; - -SpriteInfo decode_sprite(const DmaTransfer& dma) { +EyeRenderer::SpriteInfo decode_sprite(const DmaTransfer& dma) { /* (new 'static 'dma-gif-packet :dma-vif (new 'static 'dma-packet @@ -145,7 +181,7 @@ SpriteInfo decode_sprite(const DmaTransfer& dma) { ASSERT(gifTag.flg() == GifTag::Format::PACKED); ASSERT(gifTag.nreg() == 5); - SpriteInfo result; + EyeRenderer::SpriteInfo result; // rgba ASSERT(dma.data[16] == 128); // r @@ -170,25 +206,154 @@ SpriteInfo decode_sprite(const DmaTransfer& dma) { return result; } -struct EyeDraw { - SpriteInfo sprite; - ScissorInfo scissor; - std::string print() const { return fmt::format("{}\n{}\n", sprite.print(), scissor.print()); } -}; - -EyeDraw read_eye_draw(DmaFollower& dma) { +EyeRenderer::EyeDraw read_eye_draw(DmaFollower& dma) { auto scissor = decode_scissor(dma.read_and_advance()); auto sprite = decode_sprite(dma.read_and_advance()); return {sprite, scissor}; } -void EyeRenderer::draw_debug_window() { - ImGui::Text("Time: %.3f ms\n", m_average_time_ms); - ImGui::Text("Debug:\n%s", m_debug.c_str()); - ImGui::Checkbox("bilinear", &m_use_bilinear); - ImGui::Checkbox("alpha hack", &m_alpha_hack); +std::vector EyeRenderer::get_draws(DmaFollower& dma, + SharedRenderState* render_state) { + std::vector draws; + // now, loop over eyes. end condition is a 8 qw transfer to restore gs. + while (dma.current_tag().qwc != 8) { + draws.emplace_back(); + draws.emplace_back(); + + auto& l_draw = draws[draws.size() - 2]; + auto& r_draw = draws[draws.size() - 1]; + + l_draw.lr = 0; + r_draw.lr = 1; + + // eye background setup + auto adgif0_dma = dma.read_and_advance(); + ASSERT(adgif0_dma.size_bytes == 96); // 5 adgifs a+d's plus tag + ASSERT(adgif0_dma.vif0() == 0); + ASSERT(adgif0_dma.vifcode1().kind == VifCode::Kind::DIRECT); + AdgifHelper adgif0(adgif0_dma.data + 16); + auto tex0 = render_state->texture_pool->lookup_gpu_texture(adgif0.tex0().tbp0()); + + u32 pair_idx = -1; + // first draw. this is the background. It reads 0,0 of the texture uses that color everywhere. + // we'll also figure out the eye index here. + { + auto draw0 = read_eye_draw(dma); + ASSERT(draw0.sprite.uv0[0] == 0); + ASSERT(draw0.sprite.uv0[1] == 0); + ASSERT(draw0.sprite.uv1[0] == 0); + ASSERT(draw0.sprite.uv1[1] == 0); + u32 y0 = (draw0.sprite.xyz0[1] - 512) >> 4; + pair_idx = y0 / SINGLE_EYE_SIZE; + l_draw.pair = pair_idx; + r_draw.pair = pair_idx; + if (tex0->get_data_ptr()) { + u32 tex_val; + memcpy(&tex_val, tex0->get_data_ptr(), 4); + l_draw.clear_color = tex_val; + r_draw.clear_color = tex_val; + } else { + l_draw.clear_color = 0; + r_draw.clear_color = 0; + } + } + + // up next is the pupil background + { + l_draw.iris = read_eye_draw(dma); + r_draw.iris = read_eye_draw(dma); + l_draw.iris_tex = tex0; + r_draw.iris_tex = tex0; + l_draw.iris_gl_tex = *render_state->texture_pool->lookup(adgif0.tex0().tbp0()); + r_draw.iris_gl_tex = l_draw.iris_gl_tex; + } + + // now we'll draw the iris on top of that + auto test1 = dma.read_and_advance(); + (void)test1; + auto adgif1_dma = dma.read_and_advance(); + ASSERT(adgif1_dma.size_bytes == 96); // 5 adgifs a+d's plus tag + ASSERT(adgif1_dma.vif0() == 0); + ASSERT(adgif1_dma.vifcode1().kind == VifCode::Kind::DIRECT); + AdgifHelper adgif1(adgif1_dma.data + 16); + auto tex1 = render_state->texture_pool->lookup_gpu_texture(adgif1.tex0().tbp0()); + + { + l_draw.pupil = read_eye_draw(dma); + r_draw.pupil = read_eye_draw(dma); + l_draw.pupil_tex = tex1; + r_draw.pupil_tex = tex1; + l_draw.pupil_gl_tex = *render_state->texture_pool->lookup(adgif1.tex0().tbp0()); + r_draw.pupil_gl_tex = l_draw.pupil_gl_tex; + } + + // and finally the eyelid + auto test2 = dma.read_and_advance(); + (void)test2; + auto adgif2_dma = dma.read_and_advance(); + ASSERT(adgif2_dma.size_bytes == 96); // 5 adgifs a+d's plus tag + ASSERT(adgif2_dma.vif0() == 0); + ASSERT(adgif2_dma.vifcode1().kind == VifCode::Kind::DIRECT); + AdgifHelper adgif2(adgif2_dma.data + 16); + auto tex2 = render_state->texture_pool->lookup_gpu_texture(adgif2.tex0().tbp0()); + + { + l_draw.lid = read_eye_draw(dma); + r_draw.lid = read_eye_draw(dma); + l_draw.lid_tex = tex2; + r_draw.lid_tex = tex2; + l_draw.lid_gl_tex = *render_state->texture_pool->lookup(adgif2.tex0().tbp0()); + r_draw.lid_gl_tex = l_draw.lid_gl_tex; + } + + auto end = dma.read_and_advance(); + ASSERT(end.size_bytes == 0); + ASSERT(end.vif0() == 0); + ASSERT(end.vif1() == 0); + } + return draws; +} + +void EyeRenderer::handle_eye_dma2(DmaFollower& dma, + SharedRenderState* render_state, + ScopedProfilerNode&) { + Timer timer; + m_debug.clear(); + + // first should be the gs setup for render to texture + auto offset_setup = dma.read_and_advance(); + ASSERT(offset_setup.size_bytes == 128); + ASSERT(offset_setup.vifcode0().kind == VifCode::Kind::FLUSHA); + ASSERT(offset_setup.vifcode1().kind == VifCode::Kind::DIRECT); + + // next should be alpha setup + auto alpha_setup = dma.read_and_advance(); + ASSERT(alpha_setup.size_bytes == 32); + ASSERT(alpha_setup.vifcode0().kind == VifCode::Kind::NOP); + ASSERT(alpha_setup.vifcode1().kind == VifCode::Kind::DIRECT); + + // from the add to bucket + ASSERT(dma.current_tag().kind == DmaTag::Kind::NEXT); + ASSERT(dma.current_tag().qwc == 0); + ASSERT(dma.current_tag_vif0() == 0); + ASSERT(dma.current_tag_vif1() == 0); + dma.read_and_advance(); + + auto draws = get_draws(dma, render_state); + if (m_use_gpu) { + run_gpu(draws, render_state); + } else { + run_cpu(draws, render_state); + } + + float time_ms = timer.getMs(); + m_average_time_ms = m_average_time_ms * 0.95 + time_ms * 0.05; } +////////////////////// +// CPU Drawing +////////////////////// + u32 bilinear_sample_eye(const u8* tex, float tx, float ty, int texw) { int tx0 = tx; int ty0 = ty; @@ -228,7 +393,7 @@ u32 bilinear_sample_eye(const u8* tex, float tx, float ty, int texw) { template void draw_eye_impl(u32* out, - const EyeDraw& draw, + const EyeRenderer::EyeDraw& draw, const GpuTexture& tex, int pair, int lr, @@ -292,7 +457,7 @@ void draw_eye_impl(u32* out, template void draw_eye(u32* out, - const EyeDraw& draw, + const EyeRenderer::EyeDraw& draw, const GpuTexture& tex, int pair, int lr, @@ -305,170 +470,174 @@ void draw_eye(u32* out, } } -template -void EyeRenderer::handle_eye_dma2(DmaFollower& dma, - SharedRenderState* render_state, - ScopedProfilerNode&) { - Timer timer; - m_debug.clear(); - - // first should be the gs setup for render to texture - auto offset_setup = dma.read_and_advance(); - ASSERT(offset_setup.size_bytes == 128); - ASSERT(offset_setup.vifcode0().kind == VifCode::Kind::FLUSHA); - ASSERT(offset_setup.vifcode1().kind == VifCode::Kind::DIRECT); - - // next should be alpha setup - auto alpha_setup = dma.read_and_advance(); - ASSERT(alpha_setup.size_bytes == 32); - ASSERT(alpha_setup.vifcode0().kind == VifCode::Kind::NOP); - ASSERT(alpha_setup.vifcode1().kind == VifCode::Kind::DIRECT); - - // from the add to bucket - ASSERT(dma.current_tag().kind == DmaTag::Kind::NEXT); - ASSERT(dma.current_tag().qwc == 0); - ASSERT(dma.current_tag_vif0() == 0); - ASSERT(dma.current_tag_vif1() == 0); - dma.read_and_advance(); +void EyeRenderer::run_cpu(const std::vector& draws, + SharedRenderState* render_state) { + for (auto& draw : draws) { + for (auto& x : m_temp_tex) { + x = draw.clear_color; + } - // now, loop over eyes. end condition is a 8 qw transfer to restore gs. - while (dma.current_tag().qwc != 8) { - // eye background setup - auto adgif0_dma = dma.read_and_advance(); - ASSERT(adgif0_dma.size_bytes == 96); // 5 adgifs a+d's plus tag - ASSERT(adgif0_dma.vif0() == 0); - ASSERT(adgif0_dma.vifcode1().kind == VifCode::Kind::DIRECT); - AdgifHelper adgif0(adgif0_dma.data + 16); - auto tex0 = render_state->texture_pool->lookup_gpu_texture(adgif0.tex0().tbp0()); - if (DEBUG) { - m_debug += fmt::format("ADGIF0:\n{}\n", adgif0.print()); - m_debug += fmt::format("tex: {}\n", tex0->name); + if (draw.iris_tex) { + draw_eye(m_temp_tex, draw.iris, *draw.iris_tex, draw.pair, draw.lr, false, + m_use_bilinear); } - u32 pair_idx = -1; - // first draw. this is the background. It reads 0,0 of the texture uses that color everywhere. - // we'll also figure out the eye index here. - { - auto draw0 = read_eye_draw(dma); - ASSERT(draw0.sprite.uv0[0] == 0); - ASSERT(draw0.sprite.uv0[1] == 0); - ASSERT(draw0.sprite.uv1[0] == 0); - ASSERT(draw0.sprite.uv1[1] == 0); - if (DEBUG) { - m_debug += fmt::format("DRAW0\n{}", draw0.print()); - } - u32 y0 = (draw0.sprite.xyz0[1] - 512) >> 4; - pair_idx = y0 / SINGLE_EYE_SIZE; - if (tex0->get_data_ptr()) { - u32 tex_val; - memcpy(&tex_val, tex0->get_data_ptr(), 4); + if (draw.pupil_tex) { + draw_eye(m_temp_tex, draw.pupil, *draw.pupil_tex, draw.pair, draw.lr, false, + m_use_bilinear); + } - for (auto& x : m_left) { - x = tex_val; - } - for (auto& x : m_right) { - x = tex_val; - } - } + if (draw.lid_tex) { + draw_eye(m_temp_tex, draw.lid, *draw.lid_tex, draw.pair, draw.lr, draw.lr == 1, + m_use_bilinear); } - // up next is the pupil background - { - auto draw1 = read_eye_draw(dma); - auto draw2 = read_eye_draw(dma); - if (DEBUG) { - m_debug += fmt::format("DRAW1\n{}", draw1.print()); - m_debug += fmt::format("DRAW2\n{}", draw2.print()); - } - if (tex0->get_data_ptr()) { - draw_eye(m_left, draw1, *tex0, pair_idx, 0, false, m_use_bilinear); - draw_eye(m_right, draw2, *tex0, pair_idx, 1, false, m_use_bilinear); + if (m_alpha_hack) { + for (auto& a : m_temp_tex) { + a |= 0xff000000; } } - // now we'll draw the iris on top of that - auto test1 = dma.read_and_advance(); - (void)test1; - auto adgif1_dma = dma.read_and_advance(); - ASSERT(adgif1_dma.size_bytes == 96); // 5 adgifs a+d's plus tag - ASSERT(adgif1_dma.vif0() == 0); - ASSERT(adgif1_dma.vifcode1().kind == VifCode::Kind::DIRECT); - AdgifHelper adgif1(adgif1_dma.data + 16); - auto tex1 = render_state->texture_pool->lookup_gpu_texture(adgif1.tex0().tbp0()); - if (DEBUG) { - m_debug += fmt::format("ADGIF1:\n{}\n", adgif1.print()); - m_debug += fmt::format("tex: {}\n", tex1->name); - } + // update GPU: + auto& tex = m_cpu_eye_textures[draw.pair * 2 + draw.lr]; + glBindTexture(GL_TEXTURE_2D, tex.gl_tex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, + m_temp_tex); + // make sure they are still in vram + render_state->texture_pool->move_existing_to_vram(tex.gpu_tex, tex.tbp); + } +} - { - auto draw1 = read_eye_draw(dma); - auto draw2 = read_eye_draw(dma); - if (DEBUG) { - m_debug += fmt::format("DRAW1\n{}", draw1.print()); - m_debug += fmt::format("DRAW2\n{}", draw2.print()); - } - if (tex1->get_data_ptr()) { - draw_eye(m_left, draw1, *tex1, pair_idx, 0, false, m_use_bilinear); - draw_eye(m_right, draw2, *tex1, pair_idx, 1, false, m_use_bilinear); - } +int add_draw_to_buffer(int idx, const EyeRenderer::EyeDraw& draw, float* data, int pair, int lr) { + int x_off = lr * SINGLE_EYE_SIZE * 16; + int y_off = pair * SINGLE_EYE_SIZE * 16; + data[idx++] = draw.sprite.xyz0[0] - x_off; + data[idx++] = draw.sprite.xyz0[1] - y_off; + data[idx++] = 0; + data[idx++] = 0; + + data[idx++] = draw.sprite.xyz1[0] - x_off; + data[idx++] = draw.sprite.xyz0[1] - y_off; + data[idx++] = 1; + data[idx++] = 0; + + data[idx++] = draw.sprite.xyz0[0] - x_off; + data[idx++] = draw.sprite.xyz1[1] - y_off; + data[idx++] = 0; + data[idx++] = 1; + + data[idx++] = draw.sprite.xyz1[0] - x_off; + data[idx++] = draw.sprite.xyz1[1] - y_off; + data[idx++] = 1; + data[idx++] = 1; + return idx; +} + +void EyeRenderer::run_gpu(const std::vector& draws, + SharedRenderState* render_state) { + if (draws.empty()) { + return; + } + + glBindVertexArray(m_vao); + glBindBuffer(GL_ARRAY_BUFFER, m_gl_vertex_buffer); + + // the first thing we'll do is prepare the vertices + int buffer_idx = 0; + for (const auto& draw : draws) { + buffer_idx = add_draw_to_buffer(buffer_idx, draw.iris, m_gpu_vertex_buffer, draw.pair, draw.lr); + buffer_idx = + add_draw_to_buffer(buffer_idx, draw.pupil, m_gpu_vertex_buffer, draw.pair, draw.lr); + buffer_idx = add_draw_to_buffer(buffer_idx, draw.lid, m_gpu_vertex_buffer, draw.pair, draw.lr); + } + ASSERT(buffer_idx <= VTX_BUFFER_FLOATS); + int check = buffer_idx; + + // maybe buffer sub data. + glBufferData(GL_ARRAY_BUFFER, buffer_idx * sizeof(float), m_gpu_vertex_buffer, GL_STREAM_DRAW); + + FramebufferTexturePairContext ctxt(m_gpu_eye_textures[draws.front().tex_slot()].fb); + + // set up common opengl state + glDisable(GL_DEPTH_TEST); + render_state->shaders[ShaderId::EYE].activate(); + glUniform1i(glGetUniformLocation(render_state->shaders[ShaderId::EYE].id(), "tex_T0"), 0); + glActiveTexture(GL_TEXTURE0); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + buffer_idx = 0; + for (size_t draw_idx = 0; draw_idx < draws.size(); draw_idx++) { + const auto& draw = draws[draw_idx]; + const auto& out_tex = m_gpu_eye_textures[draw.tex_slot()]; + + // first, the clear + float clear[4] = {0, 0, 0, 0}; + for (int i = 0; i < 4; i++) { + clear[i] = (draw.clear_color >> (8 * i)) / 255.f; } + glClearBufferfv(GL_COLOR, 0, clear); + + // iris + if (draw.iris_tex) { + // set alpha + // set Z + // set texture + glDisable(GL_BLEND); + glBindTexture(GL_TEXTURE_2D, draw.iris_gl_tex); + glDrawArrays(GL_TRIANGLE_STRIP, buffer_idx / 4, 4); + } + buffer_idx += 4 * 4; + + if (draw.pupil_tex) { + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBindTexture(GL_TEXTURE_2D, draw.pupil_gl_tex); + glDrawArrays(GL_TRIANGLE_STRIP, buffer_idx / 4, 4); + } + buffer_idx += 4 * 4; - // and finally the eyelid - auto test2 = dma.read_and_advance(); - (void)test2; - auto adgif2_dma = dma.read_and_advance(); - ASSERT(adgif2_dma.size_bytes == 96); // 5 adgifs a+d's plus tag - ASSERT(adgif2_dma.vif0() == 0); - ASSERT(adgif2_dma.vifcode1().kind == VifCode::Kind::DIRECT); - AdgifHelper adgif2(adgif2_dma.data + 16); - auto tex2 = render_state->texture_pool->lookup_gpu_texture(adgif2.tex0().tbp0()); - if (DEBUG) { - m_debug += fmt::format("ADGIF2:\n{}\n", adgif2.print()); - m_debug += fmt::format("tex: {}\n", tex2->name); + if (draw.lid_tex) { + glDisable(GL_BLEND); + glBindTexture(GL_TEXTURE_2D, draw.lid_gl_tex); + glDrawArrays(GL_TRIANGLE_STRIP, buffer_idx / 4, 4); } + buffer_idx += 4 * 4; - { - auto draw1 = read_eye_draw(dma); - auto draw2 = read_eye_draw(dma); - if (DEBUG) { - m_debug += fmt::format("DRAW1\n{}", draw1.print()); - m_debug += fmt::format("DRAW2\n{}", draw2.print()); - } - if (tex2->get_data_ptr()) { - draw_eye(m_left, draw1, *tex2, pair_idx, 0, false, m_use_bilinear); - draw_eye(m_right, draw2, *tex2, pair_idx, 1, true, m_use_bilinear); - } + // finally, give to "vram" + render_state->texture_pool->move_existing_to_vram(out_tex.gpu_tex, out_tex.tbp); + + if (draw_idx != draws.size() - 1) { + ctxt.switch_to(m_gpu_eye_textures[draws[draw_idx + 1].tex_slot()].fb); } + } - auto end = dma.read_and_advance(); - ASSERT(end.size_bytes == 0); - ASSERT(end.vif0() == 0); - ASSERT(end.vif1() == 0); + ASSERT(check == buffer_idx); - if (m_alpha_hack) { - for (auto& a : m_left) { - a |= 0xff000000; - } + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); +} - for (auto& a : m_right) { - a |= 0xff000000; - } - } +////////////////////// +// DMA Decode +////////////////////// - // update GPU: - auto& l = m_eye_textures[pair_idx * 2]; - auto& r = m_eye_textures[pair_idx * 2 + 1]; - glBindTexture(GL_TEXTURE_2D, l.gl_tex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, - m_left); - glBindTexture(GL_TEXTURE_2D, r.gl_tex); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, - m_right); - // make sure they are still in vram - render_state->texture_pool->move_existing_to_vram(l.gpu_tex, l.tbp); - render_state->texture_pool->move_existing_to_vram(r.gpu_tex, r.tbp); - } +std::string EyeRenderer::SpriteInfo::print() const { + std::string result; + result += + fmt::format("a: {:x} uv: ({}, {}), ({}, {}) xyz: ({}, {}, {}), ({}, {}, {})", a, uv0[0], + uv0[1], uv1[0], uv1[1], xyz0[0], xyz0[1], xyz0[2], xyz1[0], xyz1[1], xyz1[2]); + return result; +} - float time_ms = timer.getMs(); - m_average_time_ms = m_average_time_ms * 0.95 + time_ms * 0.05; +std::string EyeRenderer::ScissorInfo::print() const { + return fmt::format("x : [{}, {}], y : [{}, {}]", x0, x1, y0, y1); } + +std::string EyeRenderer::EyeDraw::print() const { + return fmt::format("{}\n{}\n", sprite.print(), scissor.print()); +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/EyeRenderer.h b/game/graphics/opengl_renderer/EyeRenderer.h index 5486ff1cff..21d6a4d990 100644 --- a/game/graphics/opengl_renderer/EyeRenderer.h +++ b/game/graphics/opengl_renderer/EyeRenderer.h @@ -3,6 +3,7 @@ #include #include "game/graphics/opengl_renderer/BucketRenderer.h" #include "game/graphics/pipelines/opengl.h" +#include "game/graphics/opengl_renderer/opengl_utils.h" constexpr int EYE_BASE_BLOCK = 8160; constexpr int NUM_EYE_PAIRS = 11; @@ -11,13 +12,35 @@ constexpr int SINGLE_EYE_SIZE = 32; class EyeRenderer : public BucketRenderer { public: EyeRenderer(const std::string& name, BucketId id); + ~EyeRenderer(); void render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) override; void draw_debug_window() override; void init_textures(TexturePool& texture_pool) override; - template void handle_eye_dma2(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof); + struct SpriteInfo { + u8 a; + u32 uv0[2]; + u32 uv1[2]; + u32 xyz0[3]; + u32 xyz1[3]; + + std::string print() const; + }; + + struct ScissorInfo { + int x0, x1; + int y0, y1; + std::string print() const; + }; + + struct EyeDraw { + SpriteInfo sprite; + ScissorInfo scissor; + std::string print() const; + }; + private: std::string m_debug; float m_average_time_ms = 0; @@ -25,13 +48,52 @@ class EyeRenderer : public BucketRenderer { bool m_use_bilinear = true; bool m_alpha_hack = true; - u32 m_left[SINGLE_EYE_SIZE * SINGLE_EYE_SIZE]; - u32 m_right[SINGLE_EYE_SIZE * SINGLE_EYE_SIZE]; + u32 m_temp_tex[SINGLE_EYE_SIZE * SINGLE_EYE_SIZE]; + + bool m_use_gpu = true; - struct EyeTex { + struct CpuEyeTex { u64 gl_tex; GpuTexture* gpu_tex; u32 tbp; }; - EyeTex m_eye_textures[NUM_EYE_PAIRS * 2]; + CpuEyeTex m_cpu_eye_textures[NUM_EYE_PAIRS * 2]; + + struct GpuEyeTex { + GpuTexture* gpu_tex; + u32 tbp; + FramebufferTexturePair fb; + + // note: eye texture increased to 128x128 (originally 32x32) here. + GpuEyeTex() : fb(128, 128, GL_UNSIGNED_INT_8_8_8_8_REV) {} + } m_gpu_eye_textures[NUM_EYE_PAIRS * 2]; + + // xyst per vertex, 4 vertices per square, 3 draws per eye, 11 pairs of eyes, 2 eyes per pair. + static constexpr int VTX_BUFFER_FLOATS = 4 * 4 * 3 * NUM_EYE_PAIRS * 2; + float m_gpu_vertex_buffer[VTX_BUFFER_FLOATS]; + GLuint m_vao; + GLuint m_gl_vertex_buffer; + + struct SingleEyeDraws { + int lr; + int pair; + + int tex_slot() const { return pair * 2 + lr; } + u32 clear_color; + EyeDraw iris; + GpuTexture* iris_tex = nullptr; + u64 iris_gl_tex = 0; + + EyeDraw pupil; + GpuTexture* pupil_tex = nullptr; + u64 pupil_gl_tex = 0; + + EyeDraw lid; + GpuTexture* lid_tex = nullptr; + u64 lid_gl_tex = 0; + }; + + std::vector get_draws(DmaFollower& dma, SharedRenderState* render_state); + void run_cpu(const std::vector& draws, SharedRenderState* render_state); + void run_gpu(const std::vector& draws, SharedRenderState* render_state); }; diff --git a/game/graphics/opengl_renderer/OpenGLRenderer.cpp b/game/graphics/opengl_renderer/OpenGLRenderer.cpp index fffc7b6017..f214635f4a 100644 --- a/game/graphics/opengl_renderer/OpenGLRenderer.cpp +++ b/game/graphics/opengl_renderer/OpenGLRenderer.cpp @@ -228,6 +228,7 @@ void OpenGLRenderer::init_bucket_renderers() { m_bucket_renderers[i]->init_textures(*m_render_state.texture_pool); } sky_cpu_blender->init_textures(*m_render_state.texture_pool); + sky_gpu_blender->init_textures(*m_render_state.texture_pool); m_render_state.loader->load_common(*m_render_state.texture_pool, "GAME"); } diff --git a/game/graphics/opengl_renderer/Shader.cpp b/game/graphics/opengl_renderer/Shader.cpp index ff852f2ca4..0d69501529 100644 --- a/game/graphics/opengl_renderer/Shader.cpp +++ b/game/graphics/opengl_renderer/Shader.cpp @@ -76,4 +76,5 @@ ShaderLibrary::ShaderLibrary() { at(ShaderId::TFRAG3_NO_TEX) = {"tfrag3_no_tex"}; at(ShaderId::SPRITE3) = {"sprite3_3d"}; at(ShaderId::DIRECT2) = {"direct2"}; + at(ShaderId::EYE) = {"eye"}; } diff --git a/game/graphics/opengl_renderer/Shader.h b/game/graphics/opengl_renderer/Shader.h index 3400248c72..7e314cdb5b 100644 --- a/game/graphics/opengl_renderer/Shader.h +++ b/game/graphics/opengl_renderer/Shader.h @@ -33,6 +33,7 @@ enum class ShaderId { SPRITE = 8, SPRITE3 = 9, DIRECT2 = 10, + EYE = 11, MAX_SHADERS }; diff --git a/game/graphics/opengl_renderer/SkyBlendCPU.cpp b/game/graphics/opengl_renderer/SkyBlendCPU.cpp index 9afaab2737..181966635c 100644 --- a/game/graphics/opengl_renderer/SkyBlendCPU.cpp +++ b/game/graphics/opengl_renderer/SkyBlendCPU.cpp @@ -5,9 +5,9 @@ #include SkyBlendCPU::SkyBlendCPU() { - glGenTextures(2, m_textures); for (int i = 0; i < 2; i++) { - glBindTexture(GL_TEXTURE_2D, m_textures[i]); + glGenTextures(1, &m_textures[i].gl); + glBindTexture(GL_TEXTURE_2D, m_textures[i].gl); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_sizes[i], m_sizes[i], 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0); m_texture_data[i].resize(4 * m_sizes[i] * m_sizes[i]); @@ -15,7 +15,9 @@ SkyBlendCPU::SkyBlendCPU() { } SkyBlendCPU::~SkyBlendCPU() { - glDeleteTextures(2, m_textures); + for (auto& tex : m_textures) { + glDeleteTextures(1, &tex.gl); + } } void blend_sky_initial_fast(u8 intensity, u8* out, const u8* in, u32 size) { @@ -169,9 +171,12 @@ SkyBlendStats SkyBlendCPU::do_sky_blends(DmaFollower& dma, stats.cloud_blends++; } } - glBindTexture(GL_TEXTURE_2D, m_textures[buffer_idx]); + glBindTexture(GL_TEXTURE_2D, m_textures[buffer_idx].gl); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_sizes[buffer_idx], m_sizes[buffer_idx], 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, m_texture_data[buffer_idx].data()); + + render_state->texture_pool->move_existing_to_vram(m_textures[buffer_idx].tex, + m_textures[buffer_idx].tbp); } } @@ -181,15 +186,17 @@ SkyBlendStats SkyBlendCPU::do_sky_blends(DmaFollower& dma, void SkyBlendCPU::init_textures(TexturePool& tex_pool) { for (int i = 0; i < 2; i++) { // update it - glBindTexture(GL_TEXTURE_2D, m_textures[i]); + glBindTexture(GL_TEXTURE_2D, m_textures[i].gl); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_sizes[i], m_sizes[i], 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, m_texture_data[i].data()); TextureInput in; - in.gpu_texture = m_textures[i]; + in.gpu_texture = m_textures[i].gl; in.w = m_sizes[i]; in.h = m_sizes[i]; in.name = fmt::format("PC-SKY-CPU-{}", i); - tex_pool.give_texture_and_load_to_vram(in, SKY_TEXTURE_VRAM_ADDRS[i]); + u32 tbp = SKY_TEXTURE_VRAM_ADDRS[i]; + m_textures[i].tex = tex_pool.give_texture_and_load_to_vram(in, tbp); + m_textures[i].tbp = tbp; } } \ No newline at end of file diff --git a/game/graphics/opengl_renderer/SkyBlendCPU.h b/game/graphics/opengl_renderer/SkyBlendCPU.h index 03a0ceb9ed..77de41d1b6 100644 --- a/game/graphics/opengl_renderer/SkyBlendCPU.h +++ b/game/graphics/opengl_renderer/SkyBlendCPU.h @@ -16,7 +16,12 @@ class SkyBlendCPU { void init_textures(TexturePool& tex_pool); private: - GLuint m_textures[2]; // sky, clouds static constexpr int m_sizes[2] = {32, 64}; std::vector m_texture_data[2]; + + struct TexInfo { + GLuint gl; + u32 tbp; + GpuTexture* tex; + } m_textures[2]; }; diff --git a/game/graphics/opengl_renderer/SkyBlendGPU.cpp b/game/graphics/opengl_renderer/SkyBlendGPU.cpp index 33d64f105d..44ca9d70b9 100644 --- a/game/graphics/opengl_renderer/SkyBlendGPU.cpp +++ b/game/graphics/opengl_renderer/SkyBlendGPU.cpp @@ -66,7 +66,8 @@ void SkyBlendGPU::init_textures(TexturePool& tex_pool) { in.w = m_sizes[i]; in.h = in.w; in.name = fmt::format("PC-SKY-GPU-{}", i); - tex_pool.give_texture_and_load_to_vram(in, SKY_TEXTURE_VRAM_ADDRS[i]); + u32 tbp = SKY_TEXTURE_VRAM_ADDRS[i]; + m_tex_info[i] = {tex_pool.give_texture_and_load_to_vram(in, tbp), tbp}; } } @@ -179,6 +180,9 @@ SkyBlendStats SkyBlendGPU::do_sky_blends(DmaFollower& dma, prof.add_draw_call(1); prof.add_tri(2); + render_state->texture_pool->move_existing_to_vram(m_tex_info[buffer_idx].tex, + m_tex_info[buffer_idx].tbp); + if (buffer_idx == 0) { if (is_first_draw) { stats.sky_draws++; diff --git a/game/graphics/opengl_renderer/SkyBlendGPU.h b/game/graphics/opengl_renderer/SkyBlendGPU.h index 85fafc042a..0b43e33f4e 100644 --- a/game/graphics/opengl_renderer/SkyBlendGPU.h +++ b/game/graphics/opengl_renderer/SkyBlendGPU.h @@ -26,4 +26,9 @@ class SkyBlendGPU { }; Vertex m_vertex_data[6]; + + struct TexInfo { + GpuTexture* tex; + u32 tbp; + } m_tex_info[2]; }; \ No newline at end of file diff --git a/game/graphics/opengl_renderer/TextureUploadHandler.cpp b/game/graphics/opengl_renderer/TextureUploadHandler.cpp index ec170635de..f8e1f8ffa8 100644 --- a/game/graphics/opengl_renderer/TextureUploadHandler.cpp +++ b/game/graphics/opengl_renderer/TextureUploadHandler.cpp @@ -22,7 +22,7 @@ void TextureUploadHandler::render(DmaFollower& dma, if (dma_tag.qwc == (128 / 16)) { // note: these uploads may have texture that we need for eye rendering. flush_uploads(uploads, render_state); - render_state->eye_renderer->handle_eye_dma2(dma, render_state, prof); + render_state->eye_renderer->handle_eye_dma2(dma, render_state, prof); } auto data = dma.read_and_advance(); diff --git a/game/graphics/opengl_renderer/opengl_utils.cpp b/game/graphics/opengl_renderer/opengl_utils.cpp new file mode 100644 index 0000000000..cf90d95203 --- /dev/null +++ b/game/graphics/opengl_renderer/opengl_utils.cpp @@ -0,0 +1,57 @@ +#include "opengl_utils.h" + +#include "common/util/Assert.h" + +FramebufferTexturePair::FramebufferTexturePair(int w, int h, u64 texture_format) : m_w(w), m_h(h) { + glGenFramebuffers(1, &m_framebuffer); + glGenTextures(1, &m_texture); + + GLint old_framebuffer; + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &old_framebuffer); + + glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer); + glBindTexture(GL_TEXTURE_2D, m_texture); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, texture_format, nullptr); + + // I don't know if we really need to do this. whatever uses this texture should figure it out. + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texture, 0); + GLenum draw_buffers[1] = {GL_COLOR_ATTACHMENT0}; + glDrawBuffers(1, draw_buffers); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + ASSERT(false); + } + + glBindFramebuffer(GL_FRAMEBUFFER, old_framebuffer); +} + +FramebufferTexturePair::~FramebufferTexturePair() { + glDeleteFramebuffers(1, &m_framebuffer); + glDeleteTextures(1, &m_texture); +} + +FramebufferTexturePairContext::FramebufferTexturePairContext(FramebufferTexturePair& fb) + : m_fb(&fb) { + glGetIntegerv(GL_VIEWPORT, m_old_viewport); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_old_framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, m_fb->m_framebuffer); + glViewport(0, 0, m_fb->m_w, m_fb->m_h); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_fb->m_texture, 0); +} + +void FramebufferTexturePairContext::switch_to(FramebufferTexturePair& fb) { + if (&fb != m_fb) { + m_fb = &fb; + glBindFramebuffer(GL_FRAMEBUFFER, m_fb->m_framebuffer); + glViewport(0, 0, m_fb->m_w, m_fb->m_h); + glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_fb->m_texture, 0); + } +} + +FramebufferTexturePairContext::~FramebufferTexturePairContext() { + glViewport(m_old_viewport[0], m_old_viewport[1], m_old_viewport[2], m_old_viewport[3]); + glBindFramebuffer(GL_FRAMEBUFFER, m_old_framebuffer); +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/opengl_utils.h b/game/graphics/opengl_renderer/opengl_utils.h new file mode 100644 index 0000000000..7e02e8ee07 --- /dev/null +++ b/game/graphics/opengl_renderer/opengl_utils.h @@ -0,0 +1,39 @@ +#pragma once + +#include "game/graphics/pipelines/opengl.h" + +/*! + * This is a wrapper around a framebuffer and texture to make it easier to render to a texture. + */ +class FramebufferTexturePair { + public: + FramebufferTexturePair(int w, int h, u64 texture_format); + ~FramebufferTexturePair(); + + GLuint texture() const { return m_texture; } + + FramebufferTexturePair(const FramebufferTexturePair&) = delete; + FramebufferTexturePair& operator=(const FramebufferTexturePair&) = delete; + + private: + friend class FramebufferTexturePairContext; + GLuint m_framebuffer; + GLuint m_texture; + int m_w, m_h; +}; + +class FramebufferTexturePairContext { + public: + FramebufferTexturePairContext(FramebufferTexturePair& fb); + ~FramebufferTexturePairContext(); + + void switch_to(FramebufferTexturePair& fb); + + FramebufferTexturePairContext(const FramebufferTexturePairContext&) = delete; + FramebufferTexturePairContext& operator=(const FramebufferTexturePairContext&) = delete; + + private: + FramebufferTexturePair* m_fb; + GLint m_old_viewport[4]; + GLint m_old_framebuffer; +}; \ No newline at end of file diff --git a/game/graphics/opengl_renderer/shaders/eye.frag b/game/graphics/opengl_renderer/shaders/eye.frag new file mode 100644 index 0000000000..c69d4c5624 --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/eye.frag @@ -0,0 +1,10 @@ +#version 430 core + +out vec4 color; +in vec2 st; +uniform sampler2D tex_T0; + +void main() { + color = texture(tex_T0, st); + color.w *= 2; +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/shaders/eye.vert b/game/graphics/opengl_renderer/shaders/eye.vert new file mode 100644 index 0000000000..cc80ce7a42 --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/eye.vert @@ -0,0 +1,9 @@ +#version 430 core +layout (location = 0) in vec4 xyst_in; + +out vec2 st; + +void main() { + gl_Position = vec4((xyst_in.x - 768.f) / 256.f, (xyst_in.y - 768.f) / 256.f, 0, 1); + st = xyst_in.zw; +}