diff --git a/common/dma/dma_copy.cpp b/common/dma/dma_copy.cpp index 55f2cf654..6853735d2 100644 --- a/common/dma/dma_copy.cpp +++ b/common/dma/dma_copy.cpp @@ -44,7 +44,7 @@ void diff_dma_chains(DmaFollower ref, DmaFollower dma) { auto ref_result = ref.read_and_advance(); auto dma_result = dma.read_and_advance(); - for (int i = 0; i < ref_result.size_bytes; i++) { + for (int i = 0; i < (int)ref_result.size_bytes; i++) { if (ref_result.data[i] != dma_result.data[i]) { fmt::print("Bad data ({} vs {}) at {} into transfer: {} {}\n", ref_result.data[i], dma_result.data[i], i, ref_tag.print(), dma_tag.print()); diff --git a/game/CMakeLists.txt b/game/CMakeLists.txt index 5506a959a..a07497735 100644 --- a/game/CMakeLists.txt +++ b/game/CMakeLists.txt @@ -95,6 +95,7 @@ set(RUNTIME_SOURCE graphics/opengl_renderer/SkyRenderer.cpp graphics/opengl_renderer/SpriteRenderer.cpp graphics/opengl_renderer/TextureUploadHandler.cpp + graphics/opengl_renderer/tfrag/BufferedRenderer.cpp graphics/opengl_renderer/tfrag/program6_cpu.cpp graphics/opengl_renderer/tfrag/tfrag_unpack.cpp graphics/opengl_renderer/tfrag/TFragment.cpp diff --git a/game/graphics/opengl_renderer/DirectRenderer.cpp b/game/graphics/opengl_renderer/DirectRenderer.cpp index cdc08f347..a080c7280 100644 --- a/game/graphics/opengl_renderer/DirectRenderer.cpp +++ b/game/graphics/opengl_renderer/DirectRenderer.cpp @@ -71,6 +71,8 @@ void DirectRenderer::draw_debug_window() { ImGui::Checkbox("red", &m_debug_state.red); ImGui::SameLine(); ImGui::Checkbox("always", &m_debug_state.always_draw); + ImGui::SameLine(); + ImGui::Checkbox("no mip", &m_debug_state.disable_mipmap); if (m_mode == Mode::SPRITE_CPU) { ImGui::Checkbox("draw1", &m_sprite_mode.do_first_draw); @@ -128,6 +130,7 @@ void DirectRenderer::flush_pending(SharedRenderState* render_state, ScopedProfil m_test_state_needs_gl_update = false; } + // I think it's important that this comes last. if (m_texture_state.needs_gl_update) { update_gl_texture(render_state); m_texture_state.needs_gl_update = false; @@ -218,7 +221,10 @@ void DirectRenderer::flush_pending(SharedRenderState* render_state, ScopedProfil render_state->shaders[ShaderId::SPRITE_CPU_AFAIL].activate(); glDepthMask(GL_FALSE); glDrawArrays(GL_TRIANGLES, 0, m_prim_buffer.vert_count); - glDepthMask(GL_TRUE); + if (m_test_state.depth_writes) { + glDepthMask(GL_TRUE); + } + m_prim_gl_state_needs_gl_update = true; m_blend_state_needs_gl_update = true; draw_count++; @@ -349,7 +355,8 @@ void DirectRenderer::update_gl_texture(SharedRenderState* render_state) { } if (m_texture_state.enable_tex_filt) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + m_debug_state.disable_mipmap ? GL_LINEAR : GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } else { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); @@ -391,8 +398,9 @@ void DirectRenderer::update_gl_blend() { void DirectRenderer::update_gl_test() { const auto& state = m_test_state; - glEnable(GL_DEPTH_TEST); + if (state.zte) { + glEnable(GL_DEPTH_TEST); switch (state.ztst) { case GsTest::ZTest::NEVER: glDepthFunc(GL_NEVER); @@ -769,13 +777,14 @@ void DirectRenderer::handle_zbuf1(u64 val, assert(x.psm() == TextureFormat::PSMZ24); assert(x.zbp() == 448); - bool write = x.zmsk(); + bool write = !x.zmsk(); // assert(write); if (write != m_test_state.depth_writes) { m_stats.flush_from_zbuf++; flush_pending(render_state, prof); m_test_state_needs_gl_update = true; + m_prim_gl_state_needs_gl_update = true; m_test_state.depth_writes = write; } } @@ -1042,9 +1051,7 @@ void DirectRenderer::TestState::from_register(GsTest reg) { } zte = reg.zte(); - if (zte) { - ztst = reg.ztest(); - } + ztst = reg.ztest(); } void DirectRenderer::BlendState::from_register(GsAlpha reg) { diff --git a/game/graphics/opengl_renderer/DirectRenderer.h b/game/graphics/opengl_renderer/DirectRenderer.h index d1bb6d675..4dedafdc7 100644 --- a/game/graphics/opengl_renderer/DirectRenderer.h +++ b/game/graphics/opengl_renderer/DirectRenderer.h @@ -207,6 +207,7 @@ class DirectRenderer : public BucketRenderer { bool wireframe = false; bool red = false; bool always_draw = false; + bool disable_mipmap = true; } m_debug_state; struct { diff --git a/game/graphics/opengl_renderer/Shader.cpp b/game/graphics/opengl_renderer/Shader.cpp index 7dd83e2d2..973751997 100644 --- a/game/graphics/opengl_renderer/Shader.cpp +++ b/game/graphics/opengl_renderer/Shader.cpp @@ -73,4 +73,7 @@ ShaderLibrary::ShaderLibrary() { at(ShaderId::SPRITE_CPU_AFAIL) = {"sprite_cpu_afail"}; at(ShaderId::SKY) = {"sky"}; at(ShaderId::SKY_BLEND) = {"sky_blend"}; + at(ShaderId::DEBUG_BUFFERED) = {"debug_buffered"}; + at(ShaderId::BUFFERED_TCC0) = {"buffered_tcc0"}; + at(ShaderId::BUFFERED_TCC1) = {"buffered_tcc1"}; } \ No newline at end of file diff --git a/game/graphics/opengl_renderer/Shader.h b/game/graphics/opengl_renderer/Shader.h index cf3326c2e..f6f7dd822 100644 --- a/game/graphics/opengl_renderer/Shader.h +++ b/game/graphics/opengl_renderer/Shader.h @@ -29,8 +29,11 @@ enum class ShaderId { DEBUG_RED = 4, SPRITE_CPU = 5, SPRITE_CPU_AFAIL = 6, - SKY, - SKY_BLEND, + SKY = 7, + SKY_BLEND = 8, + DEBUG_BUFFERED = 9, + BUFFERED_TCC0 = 10, + BUFFERED_TCC1 = 11, MAX_SHADERS }; diff --git a/game/graphics/opengl_renderer/SpriteRenderer.cpp b/game/graphics/opengl_renderer/SpriteRenderer.cpp index f9d51de7b..628f6ddaf 100644 --- a/game/graphics/opengl_renderer/SpriteRenderer.cpp +++ b/game/graphics/opengl_renderer/SpriteRenderer.cpp @@ -160,7 +160,9 @@ void SpriteRenderer::render_2d_group0(DmaFollower& dma, auto run = dma.read_and_advance(); assert(run.vifcode0().kind == VifCode::Kind::NOP); assert(run.vifcode1().kind == VifCode::Kind::MSCAL); - assert(run.vifcode1().immediate == SpriteProgMem::Sprites2dGrp0); + + // HACK: this renderers 3D sprites with the 2D renderer. amazingly, it almost works. + // assert(run.vifcode1().immediate == SpriteProgMem::Sprites2dGrp0); if (m_enabled) { do_2d_group0_block_cpu(sprite_count, render_state, prof); } @@ -733,7 +735,7 @@ void SpriteRenderer::do_2d_group0_block_cpu(u32 count, memset(&packet, 0, sizeof(packet)); // ilw.y vi08, 1(vi02) | nop vi08 = matrix u32 offset_selector = m_vec_data_2d[sprite_idx].matrix(); - assert(offset_selector == 0 || offset_selector == 1); + // assert(offset_selector == 0 || offset_selector == 1); // moved this out of the loop. // lq.xyzw vf25, 900(vi00) | nop vf25 = cam_mat // lq.xyzw vf26, 901(vi00) | nop diff --git a/game/graphics/opengl_renderer/shaders/buffered_tcc0.frag b/game/graphics/opengl_renderer/shaders/buffered_tcc0.frag new file mode 100644 index 000000000..eb2236957 --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/buffered_tcc0.frag @@ -0,0 +1,18 @@ +#version 330 core + +out vec4 color; + +in vec4 fragment_color; +in vec3 tex_coord; +uniform sampler2D tex_T0; +uniform float alpha_reject; + +void main() { + vec4 T0 = texture(tex_T0, tex_coord.xy / tex_coord.z); + //vec4 T0 = textureProj(tex_T0, vec3(tex_coord.xy, 1.0)); + T0.w = 1.0; + color = fragment_color * T0 * 2.0; + if (color.a <= alpha_reject) { + discard; + } +} diff --git a/game/graphics/opengl_renderer/shaders/buffered_tcc0.vert b/game/graphics/opengl_renderer/shaders/buffered_tcc0.vert new file mode 100644 index 000000000..7f7a3e801 --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/buffered_tcc0.vert @@ -0,0 +1,14 @@ +#version 330 core + +layout (location = 0) in vec3 position_in; +layout (location = 1) in vec4 rgba_in; +layout (location = 2) in vec3 tex_coord_in; + +out vec4 fragment_color; +out vec3 tex_coord; + +void main() { + gl_Position = vec4((position_in.x - 0.5) * 16. , -(position_in.y - 0.5) * 32, position_in.z, 1.0); + fragment_color = vec4(rgba_in.x, rgba_in.y, rgba_in.z, rgba_in.a * 2); + tex_coord = tex_coord_in; +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/shaders/buffered_tcc1.frag b/game/graphics/opengl_renderer/shaders/buffered_tcc1.frag new file mode 100644 index 000000000..6a3154492 --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/buffered_tcc1.frag @@ -0,0 +1,17 @@ +#version 330 core + +out vec4 color; + +in vec4 fragment_color; +in vec3 tex_coord; +uniform sampler2D tex_T0; +uniform float alpha_reject; + +void main() { + //vec4 T0 = texture(tex_T0, tex_coord); + vec4 T0 = texture(tex_T0, tex_coord.xy / tex_coord.z); + color = fragment_color * T0 * 2.0; + if (color.a <= alpha_reject) { + discard; + } +} diff --git a/game/graphics/opengl_renderer/shaders/buffered_tcc1.vert b/game/graphics/opengl_renderer/shaders/buffered_tcc1.vert new file mode 100644 index 000000000..e520f562f --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/buffered_tcc1.vert @@ -0,0 +1,14 @@ +#version 330 core + +layout (location = 0) in vec3 position_in; +layout (location = 1) in vec4 rgba_in; +layout (location = 2) in vec3 tex_coord_in; + +out vec4 fragment_color; +out vec3 tex_coord; + +void main() { + gl_Position = vec4((position_in.x - 0.5) * 16., -(position_in.y - 0.5) * 32, position_in.z, 1.0); + fragment_color = vec4(rgba_in.x, rgba_in.y, rgba_in.z, rgba_in.w * 2.); + tex_coord = tex_coord_in; +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/shaders/debug_buffered.frag b/game/graphics/opengl_renderer/shaders/debug_buffered.frag new file mode 100644 index 000000000..3fd2c01fa --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/debug_buffered.frag @@ -0,0 +1,9 @@ +#version 330 core + +out vec4 color; + +in vec4 fragment_color; + +void main() { + color = fragment_color; +} diff --git a/game/graphics/opengl_renderer/shaders/debug_buffered.vert b/game/graphics/opengl_renderer/shaders/debug_buffered.vert new file mode 100644 index 000000000..aa8f228fc --- /dev/null +++ b/game/graphics/opengl_renderer/shaders/debug_buffered.vert @@ -0,0 +1,17 @@ +// Shader for debugging the buffered renderer. +// no texture + +#version 330 core + +layout (location = 0) in vec3 position_in; +layout (location = 1) in vec4 rgba_in; +layout (location = 2) in vec3 stq_in; + +out vec4 fragment_color; + +void main() { + // Note: position.y is multiplied by 32 instead of 16 to undo the half-height for interlacing stuff. + gl_Position = vec4((position_in.x - 0.5) * 16., -(position_in.y - 0.5) * 32, position_in.z, 1.0); + fragment_color = vec4(rgba_in.x, rgba_in.y, rgba_in.z, rgba_in.w + 0.5); + //fragment_color = vec4(1.0, 0, 0, 0.7); +} \ No newline at end of file diff --git a/game/graphics/opengl_renderer/tfrag/BufferedRenderer.cpp b/game/graphics/opengl_renderer/tfrag/BufferedRenderer.cpp new file mode 100644 index 000000000..ebd59bd5e --- /dev/null +++ b/game/graphics/opengl_renderer/tfrag/BufferedRenderer.cpp @@ -0,0 +1,732 @@ +#include "BufferedRenderer.h" +#include "common/dma/gs.h" +#include "third-party/imgui/imgui.h" +#include "game/graphics/pipelines/opengl.h" + +namespace BufferedRenderer { + +std::string DrawMode::to_string() const { + std::string result; + result += fmt::format(" depth-write: {}\n", get_depth_write_enable()); + result += fmt::format(" depth-test: "); + switch (get_depth_test()) { + case GsTest::ZTest::NEVER: + result += "never\n"; + break; + case GsTest::ZTest::GEQUAL: + result += "gequal\n"; + break; + case GsTest::ZTest::ALWAYS: + result += "always\n"; + break; + case GsTest::ZTest::GREATER: + result += "greater\n"; + break; + default: + assert(false); + } + result += fmt::format(" alpha: "); + switch (get_alpha_blend()) { + case AlphaBlend::SRC_0_SRC_DST: + result += "src, 0, src, dst\n"; + break; + case AlphaBlend::SRC_DST_SRC_DST: + result += "src, dst, src, dst\n"; + break; + case AlphaBlend::DISABLED: + result += "disabled\n"; + break; + default: + assert(false); + } + result += fmt::format(" clamp: {}\n", get_clamp_enable()); + result += fmt::format(" filt: {}\n", get_filt_enable()); + result += fmt::format(" tcc: {}\n", get_tcc_enable()); + result += fmt::format(" aref: {}\n", get_aref()); + result += fmt::format(" ate: {}\n", get_at_enable()); + result += fmt::format(" atst: "); + switch (get_alpha_test()) { + case AlphaTest::ALWAYS: + result += "always\n"; + break; + case AlphaTest::GEQUAL: + result += "gequal\n"; + break; + case AlphaTest::NEVER: + result += "never\n"; + break; + default: + assert(false); + } + result += fmt::format(" zte: {}\n", get_zt_enable()); + result += fmt::format(" abe: {}\n", get_ab_enable()); + result += fmt::format(" afail: "); + switch (get_alpha_fail()) { + case GsTest::AlphaFail::KEEP: + result += "keep\n"; + break; + case GsTest::AlphaFail::FB_ONLY: + result += "fb-only\n"; + break; + case GsTest::AlphaFail::RGB_ONLY: + result += "rgb-only\n"; + break; + case GsTest::AlphaFail::ZB_ONLY: + result += "zb-only\n"; + break; + default: + assert(false); + } + return result; +} + +Renderer::Renderer(BucketId my_id) : m_my_id(my_id) { + glGenBuffers(1, &m_ogl.vertex_buffer); + glGenBuffers(1, &m_ogl.index_buffer); + glGenVertexArrays(1, &m_ogl.vao); + + // these are some big buffers + glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer); + m_ogl.vertex_buffer_size = MAX_VERTS; + glBufferData(GL_ARRAY_BUFFER, m_ogl.vertex_buffer_size * sizeof(Vertex), nullptr, + GL_DYNAMIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ogl.index_buffer); + m_ogl.index_buffer_size = MAX_VERTS * 3; + glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_ogl.index_buffer_size * sizeof(u32), nullptr, + GL_DYNAMIC_DRAW); +} + +Renderer::~Renderer() { + glDeleteBuffers(1, &m_ogl.vertex_buffer); + glDeleteBuffers(1, &m_ogl.index_buffer); + glDeleteVertexArrays(1, &m_ogl.vao); +} + +void Renderer::render_list(const DrawList& list, + SharedRenderState* render_state, + ScopedProfilerNode& prof, + const std::vector& vertices) { + // first, load primitive buffer + glBindVertexArray(m_ogl.vao); + u32 vert_count = std::min((int)vertices.size(), (int)m_ogl.vertex_buffer_size); + if (vertices.size() > m_ogl.vertex_buffer_size) { + fmt::print("TOO MANY VERTICES: {} / {}\n", vertices.size(), m_ogl.vertex_buffer_size); + assert(false); + } + + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glEnableVertexAttribArray(3); + + glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer); + glBufferSubData(GL_ARRAY_BUFFER, 0, vert_count * sizeof(Vertex), vertices.data()); + + glVertexAttribPointer(0, // location 0 in the shader + 3, // 3 values per vert + GL_UNSIGNED_INT, // u32's + GL_TRUE, // normalized + sizeof(Vertex), // stride + (void*)offsetof(Vertex, xyz) // offset (0) + ); + + glVertexAttribPointer(1, // location 1 in the shader + 4, // 4 values per vert + GL_UNSIGNED_BYTE, // u8's + GL_TRUE, // normalized + sizeof(Vertex), // stride + (void*)offsetof(Vertex, rgba) // offset (0) + ); + + glVertexAttribPointer(2, // location 2 in the shader + 3, // 3 values per vert + GL_FLOAT, // u32's + GL_FALSE, // normalized + sizeof(Vertex), // stride + (void*)offsetof(Vertex, st) // offset (0) + ); + + glActiveTexture(GL_TEXTURE0); + + for (auto& group : list.groups) { + if (group.tbp != UINT16_MAX) { + render_group(group, render_state, prof, vertices); + } + } + + glDisableVertexAttribArray(0); + glDisableVertexAttribArray(1); + glDisableVertexAttribArray(2); + + glBindVertexArray(0); +} + +void Renderer::setup_opengl_excluding_textures(SharedRenderState* render_state, DrawMode mode) { + if (mode.get_depth_write_enable()) { + glDepthMask(GL_TRUE); + } else { + glDepthMask(GL_FALSE); + } + + if (mode.get_zt_enable()) { + glEnable(GL_DEPTH_TEST); + switch (mode.get_depth_test()) { + case GsTest::ZTest::NEVER: + glDepthFunc(GL_NEVER); + break; + case GsTest::ZTest::ALWAYS: + glDepthFunc(GL_ALWAYS); + break; + case GsTest::ZTest::GEQUAL: + glDepthFunc(GL_GEQUAL); + break; + case GsTest::ZTest::GREATER: + glDepthFunc(GL_GREATER); + break; + default: + assert(false); + } + } else { + glDisable(GL_DEPTH_TEST); + } + + if (mode.get_ab_enable() && mode.get_alpha_blend() != DrawMode::AlphaBlend::DISABLED) { + glEnable(GL_BLEND); + switch (mode.get_alpha_blend()) { + case DrawMode::AlphaBlend::SRC_DST_SRC_DST: + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case DrawMode::AlphaBlend::SRC_0_SRC_DST: + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + break; + default: + assert(false); + } + } + + if (mode.get_clamp_enable()) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + } + + if (mode.get_filt_enable()) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + + float alpha_reject = 0.; + if (mode.get_at_enable()) { + switch (mode.get_alpha_test()) { + case DrawMode::AlphaTest::ALWAYS: + break; + case DrawMode::AlphaTest::GEQUAL: + alpha_reject = mode.get_aref() / 127.f; + break; + case DrawMode::AlphaTest::NEVER: + break; + default: + assert(false); + } + } + + // todo check afail + + if (mode.get_tcc_enable()) { + render_state->shaders[ShaderId::BUFFERED_TCC1].activate(); + glUniform1f( + glGetUniformLocation(render_state->shaders[ShaderId::BUFFERED_TCC1].id(), "alpha_reject"), + alpha_reject); + glUniform1i(glGetUniformLocation(render_state->shaders[ShaderId::BUFFERED_TCC1].id(), "T0"), 0); + } else { + render_state->shaders[ShaderId::BUFFERED_TCC0].activate(); + glUniform1f( + glGetUniformLocation(render_state->shaders[ShaderId::BUFFERED_TCC0].id(), "alpha_reject"), + alpha_reject); + glUniform1i(glGetUniformLocation(render_state->shaders[ShaderId::BUFFERED_TCC0].id(), "T0"), 0); + } +} + +void Renderer::render_group(const DrawGroup& group, + SharedRenderState* render_state, + ScopedProfilerNode& prof, + const std::vector& /*vertices*/) { + TextureRecord* tex = nullptr; + + tex = render_state->texture_pool->lookup(group.tbp); + + if (!tex) { + fmt::print("Failed to find texture at {}, using random\n", group.tbp); + tex = render_state->texture_pool->get_random_texture(); + } + assert(tex); + + // first: do we need to load the texture? + if (!tex->on_gpu) { + render_state->texture_pool->upload_to_gpu(tex); + } + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, tex->gpu_texture); + + for (auto& draw : group.draws) { + if (draw.mode.is_valid()) { + m_stats.draw_calls++; + prof.add_draw_call(); + prof.add_tri(draw.triangles.size()); + render_state->shaders[ShaderId::DEBUG_BUFFERED].activate(); + setup_opengl_excluding_textures(render_state, draw.mode); + // check for overflows. + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ogl.index_buffer); + if (draw.triangles.size() * 3 > m_ogl.index_buffer_size) { + fmt::format("TOO MANY TRIS: {}/{}\n", draw.triangles.size() * 3, m_ogl.index_buffer_size); + assert(false); + } + + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, draw.triangles.size() * sizeof(u32) * 3, + draw.triangles.data()); + glDrawElements(GL_TRIANGLES, draw.triangles.size() * 3, GL_UNSIGNED_INT, (void*)0); + } + } +} + +void Renderer::draw_debug_window() { + // todo + ImGui::Text("draws: %d", m_stats.draw_calls); +} + +void Renderer::clear_stats() { + m_stats = {}; +} + +Builder::Builder(BucketId my_id) : m_my_id(my_id), m_renderer(my_id) {} + +void Builder::add_gif_data_sized(const void* data, u32 expected_size) { + if (expected_size != add_gif_data(data)) { + assert(false); // todo, might be too strict due to alignment crap + } +} + +void Builder::flush(SharedRenderState* render_state, ScopedProfilerNode& prof) { + m_renderer.render_list(m_list, render_state, prof, m_vertices); + m_list.clear(); + m_vertices.clear(); + m_cache = {}; +} + +void Builder::draw_debug_window() { + ImGui::Text("Builder: %d tri, %d vert", m_stats.m_tri, m_stats.m_dvert); + + ImGui::Text("Renderer:"); + ImGui::Separator(); + m_renderer.draw_debug_window(); +} + +void Builder::reset_state() { + m_current_mode.as_int() = 0; + m_stats = {}; + m_renderer.clear_stats(); + m_vertex_queue = {}; + m_current_mode.enable_depth_write(); + m_current_mode.enable_ab(); + m_current_mode.enable_at(); + m_current_mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST); +} + +u32 Builder::add_gif_data(const void* data_in) { + bool eop = false; + auto* data = (const u8*)data_in; + + u32 offset = 0; + while (!eop) { + GifTag tag(data + offset); + offset += 16; + + // unpack registers. + // faster to do it once outside of the nloop loop. + GifTag::RegisterDescriptor reg_desc[16]; + u32 nreg = tag.nreg(); + for (u32 i = 0; i < nreg; i++) { + reg_desc[i] = tag.reg(i); + } + + auto format = tag.flg(); + if (format == GifTag::Format::PACKED) { + if (tag.pre()) { + handle_prim(tag.prim()); + } + for (u32 loop = 0; loop < tag.nloop(); loop++) { + for (u32 reg = 0; reg < nreg; reg++) { + switch (reg_desc[reg]) { + case GifTag::RegisterDescriptor::AD: + handle_ad(data + offset); + break; + case GifTag::RegisterDescriptor::ST: + handle_st_packed(data + offset); + break; + case GifTag::RegisterDescriptor::RGBAQ: + handle_rgbaq_packed(data + offset); + break; + case GifTag::RegisterDescriptor::XYZF2: + handle_xyzf2_packed(data + offset); + break; + // case GifTag::RegisterDescriptor::PRIM: + // handle_prim_packed(data + offset, render_state, prof); + // break; + // case GifTag::RegisterDescriptor::TEX0_1: + // handle_tex0_1_packed(data + offset, render_state, prof); + // break; + default: + fmt::print("Register {} is not supported in packed mode of BufferedRenderer\n", + reg_descriptor_name(reg_desc[reg])); + assert(false); + } + offset += 16; // PACKED = quadwords + } + } + } else if (format == GifTag::Format::REGLIST) { + for (u32 loop = 0; loop < tag.nloop(); loop++) { + for (u32 reg = 0; reg < nreg; reg++) { + u64 register_data; + memcpy(®ister_data, data + offset, 8); + // fmt::print("loop: {} reg: {} {}\n", loop, reg, + // reg_descriptor_name(reg_desc[reg])); + switch (reg_desc[reg]) { + // case GifTag::RegisterDescriptor::PRIM: + // handle_prim(register_data, render_state, prof); + // break; + // case GifTag::RegisterDescriptor::RGBAQ: + // handle_rgbaq(register_data); + // break; + // case GifTag::RegisterDescriptor::XYZF2: + // handle_xyzf2(register_data, render_state, prof); + // break; + default: + fmt::print("Register {} is not supported in reglist mode of BufferedRenderer\n", + reg_descriptor_name(reg_desc[reg])); + assert(false); + } + offset += 8; // PACKED = quadwords + } + } + } else { + assert(false); // format not packed or reglist. + } + + eop = tag.eop(); + } + return offset; +} + +void Builder::handle_ad(const u8* data) { + u64 value; + GsRegisterAddress addr; + memcpy(&value, data, sizeof(u64)); + memcpy(&addr, data + 8, sizeof(GsRegisterAddress)); + + switch (addr) { + // case GsRegisterAddress::ZBUF_1: + // handle_zbuf1(value, render_state, prof); + // break; + case GsRegisterAddress::TEST_1: + handle_test1(value); + break; + case GsRegisterAddress::ALPHA_1: + handle_alpha1(value); + break; + // case GsRegisterAddress::PABE: + // handle_pabe(value); + break; + case GsRegisterAddress::CLAMP_1: + handle_clamp1(value); + break; + // case GsRegisterAddress::PRIM: + // handle_prim(value, render_state, prof); + // break; + // + case GsRegisterAddress::TEX1_1: + handle_tex1_1(value); + break; + // case GsRegisterAddress::TEXA: + // handle_texa(value); + // break; + // case GsRegisterAddress::TEXCLUT: + // // TODO + // // the only thing the direct renderer does with texture is font, which does no tricks + // with + // // CLUT. The texture upload process will do all of the lookups with the default CLUT. + // // So we'll just assume that the TEXCLUT is set properly and ignore this. + // break; + // case GsRegisterAddress::FOGCOL: + // // TODO + // break; + case GsRegisterAddress::TEX0_1: + handle_tex0_1(value); + break; + case GsRegisterAddress::MIPTBP1_1: + case GsRegisterAddress::MIPTBP2_1: + // this has the address of different mip levels, we can just ignore it. + break; + // case GsRegisterAddress::TEXFLUSH: + // break; + default: + fmt::print("Address {} is not supported in ad of BufferedRenderer\n", + register_address_name(addr)); + assert(false); + } +} + +void Builder::handle_test1(u64 val) { + // ate, atst, aref, afail, date, datm, zte, ztest + GsTest test(val); + + // ATE + m_current_mode.set_at(test.alpha_test_enable()); + + // ATST + switch (test.alpha_test()) { + case GsTest::AlphaTest::ALWAYS: + m_current_mode.set_alpha_test(DrawMode::AlphaTest::ALWAYS); + break; + case GsTest::AlphaTest::GEQUAL: + m_current_mode.set_alpha_test(DrawMode::AlphaTest::GEQUAL); + break; + case GsTest::AlphaTest::NEVER: + m_current_mode.set_alpha_test(DrawMode::AlphaTest::NEVER); + break; + default: + fmt::print("Alpha test: {} not supported\n", (int)test.alpha_test()); + assert(false); + } + + // AREF + m_current_mode.set_aref(test.aref()); + + // AFAIL + m_current_mode.set_alpha_fail(test.afail()); + + // DATE + assert(test.date() == false); + + // DATM + // who cares, if date is off + + // ZTE + m_current_mode.set_zt(test.zte()); + + // ZTST + m_current_mode.set_depth_test(test.ztest()); +} + +void Builder::handle_tex0_1(u64 val) { + GsTex0 reg(val); + + // TBP0 + m_current_tbp = reg.tbp0(); + + // TBW + // assume it's right + + // PSM + assert(reg.psm() != GsTex0::PSM::PSMT4HH); // not supported in buffered yet. + + // TW, TH + // assume it's right + + // TCC + m_current_mode.set_tcc(reg.tcc()); + + // TFX + assert(reg.tfx() == GsTex0::TextureFunction::MODULATE); + + // CBP, CPSM, CSM + // assume it's right +} + +void Builder::handle_tex1_1(u64 val) { + GsTex1 reg(val); + // ignoring these and doing our own thing! + // just need to pick between filtering and not filtering. + m_current_mode.set_filt_enable(reg.mmag()); +} + +void Builder::handle_clamp1(u64 val) { + // check that we got one of the expected ones + if (!(val == 0b101 || val == 0 || val == 1 || val == 0b100)) { + fmt::print("clamp: 0x{:x}\n", val); + assert(false); + } + + // this isn't quite right, but I'm hoping it's enough! + m_current_mode.set_clamp_enable(val == 0b101); +} + +void Builder::handle_alpha1(u64 val) { + GsAlpha reg(val); + if (reg.a_mode() == GsAlpha::BlendMode::SOURCE && reg.b_mode() == GsAlpha::BlendMode::DEST && + reg.c_mode() == GsAlpha::BlendMode::SOURCE && reg.d_mode() == GsAlpha::BlendMode::DEST) { + // (Cs - Cd) * As + Cd + // Cs * As + (1 - As) * Cd + m_current_mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_DST_SRC_DST); + + } else if (reg.a_mode() == GsAlpha::BlendMode::SOURCE && + reg.b_mode() == GsAlpha::BlendMode::ZERO_OR_FIXED && + reg.c_mode() == GsAlpha::BlendMode::SOURCE && + reg.d_mode() == GsAlpha::BlendMode::DEST) { + // (Cs - 0) * As + Cd + // Cs * As + (1) * CD + m_current_mode.set_alpha_blend(DrawMode::AlphaBlend::SRC_0_SRC_DST); + } else { + // unsupported blend: a 0 b 2 c 2 d 1 + fmt::print("unsupported blend: a {} b {} c {} d {}\n", (int)reg.a_mode(), (int)reg.b_mode(), + (int)reg.c_mode(), (int)reg.d_mode()); + assert(false); + } +} + +void Builder::handle_prim(u64 val) { + GsPrim prim(val); + + // PRIM + m_prim_kind = prim.kind(); + + // IIP + assert(prim.gouraud()); + + // TME + assert(prim.tme()); + + // FGE + // TODO fog + + // ABE + m_current_mode.set_ab(prim.abe()); + + // AA1 + assert(!prim.aa1()); + + // FST + assert(!prim.fst()); + + // CTXT + assert(!prim.ctxt()); + + // FIX + assert(!prim.fix()); + + if (m_vertex_queue.startup) { + m_vertex_queue.startup = 0; + m_vertex_queue.idx = 0; + } else { + // assert(false); + } +} + +void Builder::handle_st_packed(const u8* data) { + memcpy(m_st_pending_q, data, 3 * sizeof(float)); +} + +void Builder::handle_rgbaq_packed(const u8* data) { + m_q = m_st_pending_q[2]; + // memcpy(m_rgba.data(), data, 4); + m_rgba[0] = data[0]; + m_rgba[1] = data[4]; + m_rgba[2] = data[8]; + m_rgba[3] = data[12]; +} + +void Builder::handle_xyzf2_packed(const u8* data) { + u32 x, y; + memcpy(&x, data, 4); + memcpy(&y, data + 4, 4); + + u64 upper; + memcpy(&upper, data + 8, 8); + u32 z = (upper >> 4) & 0xffffff; + + u8 f = (upper >> 36); + bool adc = upper & (1ull << 47); + handle_xyzf2_common(x, y, z, f, !adc); +} + +void Builder::handle_xyzf2_common(u32 x, u32 y, u32 z, u8 /*f*/, bool advance) { + // first, create a vertex: + u32 new_vertex = create_vertex_now(x, y, z); + + // next, add that vertex. This will inform us if we actually draw a prim or not + bool new_prim = false; + switch (m_prim_kind) { + case GsPrim::Kind::TRI_STRIP: + new_prim = handle_tri_strip_add(new_vertex, advance); + break; + default: + assert(false); + } + + if (new_prim) { + // todo, winding order? + add_prim_now({m_vertex_queue.verts[0], m_vertex_queue.verts[1], m_vertex_queue.verts[2]}); + } +} + +void Builder::add_prim_now(Triangle tri) { + if (m_cache.last_tbp == m_current_tbp && m_cache.last_mode == m_current_mode.as_int()) { + m_cache.draw->triangles.push_back(tri); + } else { + auto group = m_list.get_group_for_tbp(m_current_tbp); + assert(group); + // todo flush and stats + + auto draw = group->get_draw_for_mode(m_current_mode); + assert(draw); + // todo flush and stats + draw->triangles.push_back(tri); + m_cache.last_mode = m_current_mode.as_int(); + m_cache.last_tbp = m_current_tbp; + m_cache.draw = draw; + } + + m_stats.m_tri++; +} + +u32 Builder::create_vertex_now(u32 x, u32 y, u32 z) { + m_stats.m_dvert++; + u32 idx = m_vertices.size(); + m_vertices.emplace_back(); + auto& vert = m_vertices.back(); + vert.xyz = math::Vector(x << 16, y << 16, z << 8); + vert.rgba = m_rgba; + vert.st = math::Vector(m_st_pending_q[0], m_st_pending_q[1]); + vert.q = m_q; + return idx; +} + +bool Builder::handle_tri_strip_add(u32 new_vertex, bool advance) { + m_vertex_queue.verts[m_vertex_queue.idx] = new_vertex; + m_vertex_queue.idx++; + + // wrap the index + if (m_vertex_queue.idx == 3) { + m_vertex_queue.idx = 0; + } + + // bump the startup + if (m_vertex_queue.startup < 3) { + m_vertex_queue.startup++; + } + + if (m_vertex_queue.startup >= 3) { + if (advance) { + return true; + } + } + return false; +} + +} // namespace BufferedRenderer diff --git a/game/graphics/opengl_renderer/tfrag/BufferedRenderer.h b/game/graphics/opengl_renderer/tfrag/BufferedRenderer.h new file mode 100644 index 000000000..c869736d6 --- /dev/null +++ b/game/graphics/opengl_renderer/tfrag/BufferedRenderer.h @@ -0,0 +1,347 @@ +#pragma once +#include "common/math/Vector.h" +#include "common/common_types.h" +#include "game/graphics/opengl_renderer/BucketRenderer.h" +#include "common/dma/gs.h" +#include "game/graphics/pipelines/opengl.h" + +namespace BufferedRenderer { + +// The buffered renderer performs efficient sorting of primitives to reduce draw calls. + +// the settings: + +// this is the maximum number of draw "kinds" we can have per textures. +// a draw is a different "kind" if any opengl state changes happen (like z test, etc) +// if this is exceeded, this will return an error code. +// you must flush the group, then try adding it again. +// making this too large will slow down insertion and increase memory usage +constexpr int MAX_DRAW_KINDS_PER_TEX = 4; + +// this is the maximum number of textures. If this is exceeded, there will be an error like above. +constexpr int MAX_TEXTURES = 256; + +// this is the PS2 maximum TBP value. +constexpr int MAX_TBP = 16384; + +// 32-byte vertex. +// the xyz, rgba, and stq are aligned. we have a free 4-bytes at the end. +// there is a single big pool of vertices. +struct Vertex { + math::Vector xyz; // ps2 coords (0) + math::Vector rgba; // 0, 4 (1) + math::Vector st; // (2) + float q; // (3) + u32 pad; +}; +static_assert(sizeof(Vertex) == 32); + +// a triangle grabs three vertices from the pools. +struct Triangle { + u32 verts[3]; +}; + +// this represents all of the drawing state, stored as an integer. +// it can also represent "invalid". +class DrawMode { + public: + enum class AlphaBlend { + DISABLED = 0, + SRC_DST_SRC_DST = 1, + SRC_0_SRC_DST = 2, + }; + + enum class AlphaTest { + NEVER = 0, + ALWAYS = 1, + GEQUAL = 2, + }; + + bool get_depth_write_enable() const { return m_val & 0b1; } + void enable_depth_write() { m_val = m_val | 0b1; } + void disable_depth_write() { m_val = m_val & ~(0b1); } + + GsTest::ZTest get_depth_test() const { return (GsTest::ZTest)((m_val >> 1) & 0b11); } + void set_depth_test(GsTest::ZTest dt) { m_val = (m_val & ~(0b110)) | ((u32)(dt) << 1); } + + AlphaBlend get_alpha_blend() const { return (AlphaBlend)((m_val >> 3) & 0b11); } + void set_alpha_blend(AlphaBlend ab) { m_val = (m_val & ~(0b11000)) | ((u32)(ab) << 3); } + + u8 get_aref() const { return m_val >> 8; } + void set_aref(u8 val) { m_val = (m_val & ~(0xff00)) | (val << 8); } + + AlphaTest get_alpha_test() const { return (AlphaTest)((m_val >> 16) & 0b11); } + void set_alpha_test(AlphaTest ab) { m_val = (m_val & ~(0b11 << 16)) | ((u32)(ab) << 16); } + + GsTest::AlphaFail get_alpha_fail() const { return (GsTest::AlphaFail)((m_val >> 21) & 0b11); } + void set_alpha_fail(GsTest::AlphaFail ab) { m_val = (m_val & ~(0b11 << 21)) | ((u32)(ab) << 21); } + + bool is_invalid() const { return m_val == UINT32_MAX; } + bool is_valid() const { return !is_invalid(); } + void set_invalid() { m_val = UINT32_MAX; } + + bool get_clamp_enable() const { return m_val & (1 << 5); } + void set_clamp_enable(bool en) { + if (en) { + enable_clamp(); + } else { + disable_clamp(); + } + } + void enable_clamp() { m_val = m_val | (1 << 5); } + void disable_clamp() { m_val = m_val & (~(1 << 5)); } + + bool get_filt_enable() const { return m_val & (1 << 6); } + void enable_filt() { m_val = m_val | (1 << 6); } + void disable_filt() { m_val = m_val & (~(1 << 6)); } + void set_filt_enable(bool en) { + if (en) { + enable_filt(); + } else { + disable_filt(); + } + } + + bool get_tcc_enable() const { return m_val & (1 << 6); } + void enable_tcc() { m_val = m_val | (1 << 7); } + void disable_tcc() { m_val = m_val & (~(1 << 7)); } + void set_tcc(bool en) { + if (en) { + enable_tcc(); + } else { + disable_tcc(); + } + } + + bool get_at_enable() const { return m_val & (1 << 18); } + void enable_at() { m_val = m_val | (1 << 18); } + void disable_at() { m_val = m_val & (~(1 << 18)); } + void set_at(bool en) { + if (en) { + enable_at(); + } else { + disable_at(); + } + } + + bool get_zt_enable() const { return m_val & (1 << 19); } + void enable_zt() { m_val = m_val | (1 << 19); } + void disable_zt() { m_val = m_val & (~(1 << 19)); } + void set_zt(bool en) { + if (en) { + enable_zt(); + } else { + disable_zt(); + } + } + + bool get_ab_enable() const { return m_val & (1 << 20); } + void enable_ab() { m_val = m_val | (1 << 20); } + void disable_ab() { m_val = m_val & (~(1 << 20)); } + void set_ab(bool en) { + if (en) { + enable_ab(); + } else { + disable_ab(); + } + } + + u32& as_int() { return m_val; } + + bool operator==(const DrawMode& other) const { return m_val == other.m_val; } + bool operator!=(const DrawMode& other) const { return m_val != other.m_val; } + + std::string to_string() const; + + private: + // 0 - depth write enable + // 1, 2 - test: never, always, gequal, greater + // 3, 4 - alpha: disable, [src,dst,src,dst], [src,0,src,dst], XX + // 5 - clamp enable + // 6 - filt enable + // 7 - tcc enable + // 8,9,10,11,12,14,14,15 - aref + // 16, 17 - atest + // 18 - ate + // 19 - zte + // 20 - abe + // 21, 22 - afail + u32 m_val = UINT32_MAX; +}; + +struct Draw { + DrawMode mode; + std::vector triangles; // just indices + void clear() { + mode.set_invalid(); + triangles.clear(); + } +}; + +struct DrawGroup { + Draw draws[MAX_DRAW_KINDS_PER_TEX]; + u16 tbp = UINT16_MAX; + + // can fail, in which case you should flush the DrawGroup. + Draw* get_draw_for_mode(DrawMode mode) { + for (auto& draw : draws) { + if (draw.mode.is_invalid()) { + draw.mode = mode; + return &draw; + } else if (draw.mode == mode) { + return &draw; + } + } + return nullptr; + } + + void clear() { + for (auto& draw : draws) { + draw.clear(); + tbp = UINT16_MAX; + } + } +}; + +struct DrawList { + DrawGroup groups[MAX_TEXTURES]; + u16 tbp_to_tex_id[MAX_TBP]; + u16 current_tex_id = 0; + + DrawList() { + for (auto& x : tbp_to_tex_id) { + x = UINT16_MAX; + } + } + + void clear() { + for (auto& group : groups) { + group.clear(); + } + for (auto& x : tbp_to_tex_id) { + x = UINT16_MAX; + } + current_tex_id = 0; + } + + DrawGroup* get_group_for_tbp(u16 tbp) { + if (tbp_to_tex_id[tbp] != UINT16_MAX) { + // already have it + return &groups[tbp_to_tex_id[tbp]]; + } else { + if (current_tex_id == MAX_TEXTURES) { + // don't have it, and out of room + return nullptr; + } else { + // don't have it, but we can add it. + auto group = &groups[current_tex_id]; + group->tbp = tbp; + tbp_to_tex_id[tbp] = current_tex_id; + current_tex_id++; + return group; + } + } + } +}; + +class Renderer { + public: + Renderer(BucketId my_id); + ~Renderer(); + void render_list(const DrawList& list, + SharedRenderState* render_state, + ScopedProfilerNode& prof, + const std::vector& vertices); + void render_group(const DrawGroup& group, + SharedRenderState* render_state, + ScopedProfilerNode& prof, + const std::vector& vertices); + + void setup_opengl_excluding_textures(SharedRenderState* render_state, DrawMode mode); + + void draw_debug_window(); + void clear_stats(); + BucketId my_id() const { return m_my_id; } + + private: + static constexpr int MAX_VERTS = 400000; + BucketId m_my_id; + + struct { + int triangles = 0; + int draw_calls = 0; + int groups = 0; + } m_stats; + + struct { + GLuint vertex_buffer = -1; + u32 vertex_buffer_size = 0; + GLuint index_buffer = -1; + u32 index_buffer_size = 0; + GLuint vao = -1; + } m_ogl; +}; + +class Builder { + public: + Builder(BucketId my_id); + u32 add_gif_data(const void* data); + void add_gif_data_sized(const void* data, u32 expected_size); + void flush(SharedRenderState* render_state, ScopedProfilerNode& prof); + void draw_debug_window(); + void reset_state(); + + private: + void handle_ad(const u8* data); + void handle_test1(u64 val); + void handle_tex0_1(u64 val); + void handle_tex1_1(u64 val); + void handle_clamp1(u64 val); + void handle_prim(u64 val); + void handle_alpha1(u64 val); + + void handle_st_packed(const u8* data); + void handle_rgbaq_packed(const u8* data); + void handle_xyzf2_packed(const u8* data); + void handle_xyzf2_common(u32 x, u32 y, u32 z, u8 f, bool advance); + bool handle_tri_strip_add(u32 new_vertex, bool advance); + void add_prim_now(Triangle tri); + + u32 create_vertex_now(u32 x, u32 y, u32 z); + BucketId my_id() const { return m_my_id; } + + BucketId m_my_id; + + DrawList m_list; + Renderer m_renderer; + DrawMode m_current_mode; + u16 m_current_tbp = 0; + + GsPrim::Kind m_prim_kind = GsPrim::Kind::PRIM_7; + + std::vector m_vertices; + + float m_st_pending_q[3] = {0}; // q goes to real q on rgbaq packed + float m_q = 0; + math::Vector m_rgba; + + // todo maybe add a mode cache? + + struct { + u32 idx = 0; + u32 startup = 0; + u32 verts[3] = {0, 0, 0}; + } m_vertex_queue; + + struct { + u32 m_dvert = 0; + u32 m_tri = 0; + } m_stats; + + struct { + u32 last_tbp = UINT32_MAX; + u32 last_mode = UINT32_MAX; + Draw* draw = nullptr; + } m_cache; +}; +} // namespace BufferedRenderer diff --git a/game/graphics/opengl_renderer/tfrag/TFragment.cpp b/game/graphics/opengl_renderer/tfrag/TFragment.cpp index 419ed750d..2e022c4c8 100644 --- a/game/graphics/opengl_renderer/tfrag/TFragment.cpp +++ b/game/graphics/opengl_renderer/tfrag/TFragment.cpp @@ -18,7 +18,8 @@ bool looks_like_tfrag_init(const DmaFollower& follow) { TFragment::TFragment(const std::string& name, BucketId my_id, bool child_mode) : BucketRenderer(name, my_id), m_child_mode(child_mode), - m_direct_renderer(fmt::format("{}.direct", name), my_id, 1024, DirectRenderer::Mode::NORMAL) { + m_direct_renderer(fmt::format("{}.direct", name), my_id, 1024, DirectRenderer::Mode::NORMAL), + m_buffered_renderer(my_id) { for (auto& buf : m_buffered_data) { for (auto& x : buf.pad) { x = 0xff; @@ -35,7 +36,12 @@ void TFragment::render(DmaFollower& dma, ScopedProfilerNode& prof) { m_debug_string.clear(); m_frag_debug.clear(); - m_direct_renderer.reset_state(); + if (m_use_buffered_renderer) { + m_buffered_renderer.reset_state(); + } else { + m_direct_renderer.reset_state(); + } + m_stats = {}; if (!m_enabled) { @@ -101,7 +107,12 @@ void TFragment::render(DmaFollower& dma, } m_debug_string += fmt::format("fail: {}\n", dma.current_tag().print()); - m_direct_renderer.flush_pending(render_state, prof); + + if (m_use_buffered_renderer) { + m_buffered_renderer.flush(render_state, prof); + } else { + m_direct_renderer.flush_pending(render_state, prof); + } while (dma.current_tag_offset() != render_state->next_bucket) { auto tag = dma.current_tag().print(); @@ -124,6 +135,7 @@ void TFragment::draw_debug_window() { ImGui::Checkbox("Prog10 hack", &m_prog10_with_prog6); ImGui::Checkbox("Prog18 hack", &m_prog18_with_prog6); ImGui::Checkbox("Others with prog6", &m_all_with_prog6); + ImGui::Checkbox("Use Buffered Renderer", &m_use_buffered_renderer); ImGui::Text("packets: %d", m_stats.tfrag_dma_packets); ImGui::Text("frag bytes: %d", m_stats.tfrag_bytes); ImGui::Text("errors: %d", m_stats.error_packets); @@ -131,11 +143,16 @@ void TFragment::draw_debug_window() { ImGui::Text(" prog %d: %d calls\n", prog, m_stats.per_program[prog].calls); } - if (ImGui::TreeNode("direct")) { + if (!m_use_buffered_renderer && ImGui::TreeNode("direct")) { m_direct_renderer.draw_debug_window(); ImGui::TreePop(); } + if (m_use_buffered_renderer && ImGui::TreeNode("buffered")) { + m_buffered_renderer.draw_debug_window(); + ImGui::TreePop(); + } + ImGui::TextUnformatted(m_debug_string.data()); } @@ -149,7 +166,11 @@ void TFragment::handle_initialization(DmaFollower& dma, assert(setup_test.vifcode1().immediate == 2); assert(setup_test.size_bytes == 32); memcpy(m_test_setup, setup_test.data, 32); - m_direct_renderer.render_gif(m_test_setup, 32, render_state, prof); + if (m_use_buffered_renderer) { + m_buffered_renderer.add_gif_data_sized(m_test_setup, 32); + } else { + m_direct_renderer.render_gif(m_test_setup, 32, render_state, prof); + } // matrix 0 auto mat0_upload = dma.read_and_advance(); diff --git a/game/graphics/opengl_renderer/tfrag/TFragment.h b/game/graphics/opengl_renderer/tfrag/TFragment.h index 218345ebf..94f568625 100644 --- a/game/graphics/opengl_renderer/tfrag/TFragment.h +++ b/game/graphics/opengl_renderer/tfrag/TFragment.h @@ -2,6 +2,7 @@ #include "game/graphics/opengl_renderer/BucketRenderer.h" #include "game/graphics/opengl_renderer/DirectRenderer.h" +#include "game/graphics/opengl_renderer/tfrag/BufferedRenderer.h" #include "common/dma/gs.h" #include "common/math/Vector.h" @@ -191,6 +192,7 @@ class TFragment : public BucketRenderer { bool m_prog10_with_prog6 = true; bool m_prog18_with_prog6 = true; bool m_all_with_prog6 = false; + bool m_use_buffered_renderer = true; std::string m_frag_debug; // GS setup data @@ -278,4 +280,5 @@ class TFragment : public BucketRenderer { } m_stats; DirectRenderer m_direct_renderer; + BufferedRenderer::Builder m_buffered_renderer; }; diff --git a/game/graphics/opengl_renderer/tfrag/program6_cpu.cpp b/game/graphics/opengl_renderer/tfrag/program6_cpu.cpp index 2b132be88..48d65da4b 100644 --- a/game/graphics/opengl_renderer/tfrag/program6_cpu.cpp +++ b/game/graphics/opengl_renderer/tfrag/program6_cpu.cpp @@ -1162,8 +1162,13 @@ void TFragment::XGKICK(u32 addr, SharedRenderState* render_state, ScopedProfiler assert(addr < KICK_ZONE_END); if (!m_skip_xgkick) { - m_direct_renderer.render_gif(&m_kick_data.pad[(addr - TFragDataMem::TFragKickZoneData) * 16], - UINT32_MAX, render_state, prof); + if (m_use_buffered_renderer) { + m_buffered_renderer.add_gif_data( + &m_kick_data.pad[(addr - TFragDataMem::TFragKickZoneData) * 16]); + } else { + m_direct_renderer.render_gif(&m_kick_data.pad[(addr - TFragDataMem::TFragKickZoneData) * 16], + UINT32_MAX, render_state, prof); + } } } diff --git a/game/graphics/pipelines/opengl.cpp b/game/graphics/pipelines/opengl.cpp index 218a7eefa..61e8b6ddd 100644 --- a/game/graphics/pipelines/opengl.cpp +++ b/game/graphics/pipelines/opengl.cpp @@ -139,6 +139,7 @@ static std::shared_ptr gl_make_main_display(int width, } glfwMakeContextCurrent(window); + gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); std::string image_path = fmt::format("{}/docs/favicon-nobg.png", file_util::get_project_path()); diff --git a/game/graphics/texture/TexturePool.cpp b/game/graphics/texture/TexturePool.cpp index 6d5e6ddf2..7f625e398 100644 --- a/game/graphics/texture/TexturePool.cpp +++ b/game/graphics/texture/TexturePool.cpp @@ -60,10 +60,6 @@ void TextureRecord::serialize(Serializer& ser) { ser.from_ptr(&gpu_texture); ser.from_ptr(&dest); ser.from_pod_vector(&data); - ser.from_ptr(&min_a_zero); - ser.from_ptr(&max_a_zero); - ser.from_ptr(&min_a_nonzero); - ser.from_ptr(&max_a_nonzero); if (ser.is_loading()) { gpu_texture = -1; @@ -242,10 +238,6 @@ std::vector> TexturePool::convert_textures(const min_a_zero = std::min(min_a_zero, a); } } - texture_record->max_a_zero = max_a_zero; - texture_record->min_a_zero = min_a_zero; - texture_record->max_a_nonzero = max_a_nonzero; - texture_record->min_a_nonzero = min_a_nonzero; // Debug output. if (dump_textures_to_file) { @@ -409,6 +401,12 @@ void TexturePool::upload_to_gpu(TextureRecord* tex) { // we have to set these, imgui won't do it automatically glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex->gpu_texture); + glGenerateMipmap(GL_TEXTURE_2D); + + float aniso = 0.0f; + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY, &aniso); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY, aniso); + 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); diff --git a/game/graphics/texture/TexturePool.h b/game/graphics/texture/TexturePool.h index 650f9614b..aaeb38749 100644 --- a/game/graphics/texture/TexturePool.h +++ b/game/graphics/texture/TexturePool.h @@ -7,22 +7,55 @@ #include "game/graphics/texture/TextureConverter.h" #include "common/util/Serializer.h" +// Converting textures happens when textures are uploaded by the game. +// Uploading textures to the gpu and creating samplers is done lazily, as needed. + +// Each sampler +struct TextureSampler { + u64 sampler_object = -1; // the opengl sampler + u64 handle = -1; // the handle used for bindless textures. + bool created = false; // lazily created as needed, by default we don't make them +}; + +// Each texture in our pool has a record: struct TextureRecord { - std::string page_name; - std::string name; - u8 mip_level; - u8 psm = -1; - u8 cpsm = -1; - u16 w, h; - u8 data_segment; - bool on_gpu = false; - bool do_gc = true; // if set, will be unloaded from GPU when another is upload on top + std::string page_name; // the page we belong to (from game info) + std::string name; // our name (from game info) + u8 mip_level; // which mip we are + u8 psm = -1; // format in the game + u8 cpsm = -1; // clut format in the game + u16 w, h; // our dimensions + u8 data_segment; // which segment we came from in the texture page + bool on_gpu = false; // if we are uploaded to the GPU + + // garbage collection settings. + // by default, do_gc is set, and the pool will take care of freeing textures. + // when a texture is uploaded on top of a texture (in PS2 VRAM), the texture will be unloaded from + // the GPU. Unless somebody has another instance of the shared_ptr to this texture, this structure + // (including converted texture data) will be discarded. + + // In some cases, this is not desirable because the game may toggle between two different textures + // in VRAM, and we don't want to reconvert every time. The TextureUploadHandler will implement its + // own caching to help with this. To manage textures yourself, you should: + // - keep around a shared_ptr to the TextureRecord (so it doesn't get deleted when it's out of PS2 + // VRAM). + // - set do_gc to false (to keep texture in GPU memory when replaced). + // In this case, you must use the discard function to remove the texture from the GPU, if you + // really want to get rid of it. + bool do_gc = true; + + // The texture data. In some cases, we keep textures only on the GPU (for example, the result of a + // render to texture). In these, data is not populated, but you must set only_on_gpu = true. When + // saving graphics state, the texture will be dumped from the GPU and saved to the file so it is + // possible to restore. bool only_on_gpu = false; std::vector data; - u64 gpu_texture = 0; - u32 dest = -1; - u8 min_a_zero, max_a_zero, min_a_nonzero, max_a_nonzero; + // if we're on the gpu, our OpenGL texture + u64 gpu_texture = 0; + + // our VRAM address. + u32 dest = -1; void unload_from_gpu(); diff --git a/game/system/newpad.cpp b/game/system/newpad.cpp index ca0ae4f3a..d62eb3676 100644 --- a/game/system/newpad.cpp +++ b/game/system/newpad.cpp @@ -119,7 +119,7 @@ int IsPressed(MappingInfo& mapping, Button button, int pad = 0) { // returns the value of the analog axis (in the future, likely pressure sensitive if we support it?) // if invalid or otherwise -- returns 127 (analog stick neutral position) -int AnalogValue(MappingInfo& mapping, Analog analog, int pad = 0) { +int AnalogValue(MappingInfo& /*mapping*/, Analog analog, int pad = 0) { if (CheckPadIdx(pad) == -1) { return 127; } diff --git a/goal_src/dgos/bea.gd b/goal_src/dgos/bea.gd index 1a5eb2258..7d12048cd 100644 --- a/goal_src/dgos/bea.gd +++ b/goal_src/dgos/bea.gd @@ -50,4 +50,4 @@ ("sharkey-ag-BEA-TRA-VI2.go" "sharkey") ("windmill-one-ag.go" "windmill-one") ("beach-vis.go" "beach-vis") - ) \ No newline at end of file + ) diff --git a/third-party/glad/include/glad/glad.h b/third-party/glad/include/glad/glad.h index d95c6f3e3..30483d85d 100644 --- a/third-party/glad/include/glad/glad.h +++ b/third-party/glad/include/glad/glad.h @@ -1,22 +1,23 @@ /* - OpenGL loader generated by glad 0.1.34 on Wed Oct 6 20:57:47 2021. + OpenGL loader generated by glad 0.1.34 on Sun Nov 14 17:13:20 2021. Language/Generator: C/C++ Specification: gl APIs: gl=4.3 Profile: compatibility Extensions: - + GL_ARB_bindless_texture, + GL_ARB_texture_filter_anisotropic Loader: True Local files: False Omit khrplatform: False Reproducible: False Commandline: - --profile="compatibility" --api="gl=4.3" --generator="c" --spec="gl" --extensions="" + --profile="compatibility" --api="gl=4.3" --generator="c" --spec="gl" --extensions="GL_ARB_bindless_texture,GL_ARB_texture_filter_anisotropic" Online: - https://glad.dav1d.de/#profile=compatibility&language=c&specification=gl&loader=on&api=gl%3D4.3 + https://glad.dav1d.de/#profile=compatibility&language=c&specification=gl&loader=on&api=gl%3D4.3&extensions=GL_ARB_bindless_texture&extensions=GL_ARB_texture_filter_anisotropic */ @@ -4669,6 +4670,65 @@ typedef void (APIENTRYP PFNGLGETOBJECTPTRLABELPROC)(const void *ptr, GLsizei buf GLAPI PFNGLGETOBJECTPTRLABELPROC glad_glGetObjectPtrLabel; #define glGetObjectPtrLabel glad_glGetObjectPtrLabel #endif +#define GL_UNSIGNED_INT64_ARB 0x140F +#define GL_TEXTURE_MAX_ANISOTROPY 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY 0x84FF +#ifndef GL_ARB_bindless_texture +#define GL_ARB_bindless_texture 1 +GLAPI int GLAD_GL_ARB_bindless_texture; +typedef GLuint64 (APIENTRYP PFNGLGETTEXTUREHANDLEARBPROC)(GLuint texture); +GLAPI PFNGLGETTEXTUREHANDLEARBPROC glad_glGetTextureHandleARB; +#define glGetTextureHandleARB glad_glGetTextureHandleARB +typedef GLuint64 (APIENTRYP PFNGLGETTEXTURESAMPLERHANDLEARBPROC)(GLuint texture, GLuint sampler); +GLAPI PFNGLGETTEXTURESAMPLERHANDLEARBPROC glad_glGetTextureSamplerHandleARB; +#define glGetTextureSamplerHandleARB glad_glGetTextureSamplerHandleARB +typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLERESIDENTARBPROC)(GLuint64 handle); +GLAPI PFNGLMAKETEXTUREHANDLERESIDENTARBPROC glad_glMakeTextureHandleResidentARB; +#define glMakeTextureHandleResidentARB glad_glMakeTextureHandleResidentARB +typedef void (APIENTRYP PFNGLMAKETEXTUREHANDLENONRESIDENTARBPROC)(GLuint64 handle); +GLAPI PFNGLMAKETEXTUREHANDLENONRESIDENTARBPROC glad_glMakeTextureHandleNonResidentARB; +#define glMakeTextureHandleNonResidentARB glad_glMakeTextureHandleNonResidentARB +typedef GLuint64 (APIENTRYP PFNGLGETIMAGEHANDLEARBPROC)(GLuint texture, GLint level, GLboolean layered, GLint layer, GLenum format); +GLAPI PFNGLGETIMAGEHANDLEARBPROC glad_glGetImageHandleARB; +#define glGetImageHandleARB glad_glGetImageHandleARB +typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLERESIDENTARBPROC)(GLuint64 handle, GLenum access); +GLAPI PFNGLMAKEIMAGEHANDLERESIDENTARBPROC glad_glMakeImageHandleResidentARB; +#define glMakeImageHandleResidentARB glad_glMakeImageHandleResidentARB +typedef void (APIENTRYP PFNGLMAKEIMAGEHANDLENONRESIDENTARBPROC)(GLuint64 handle); +GLAPI PFNGLMAKEIMAGEHANDLENONRESIDENTARBPROC glad_glMakeImageHandleNonResidentARB; +#define glMakeImageHandleNonResidentARB glad_glMakeImageHandleNonResidentARB +typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64ARBPROC)(GLint location, GLuint64 value); +GLAPI PFNGLUNIFORMHANDLEUI64ARBPROC glad_glUniformHandleui64ARB; +#define glUniformHandleui64ARB glad_glUniformHandleui64ARB +typedef void (APIENTRYP PFNGLUNIFORMHANDLEUI64VARBPROC)(GLint location, GLsizei count, const GLuint64 *value); +GLAPI PFNGLUNIFORMHANDLEUI64VARBPROC glad_glUniformHandleui64vARB; +#define glUniformHandleui64vARB glad_glUniformHandleui64vARB +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64ARBPROC)(GLuint program, GLint location, GLuint64 value); +GLAPI PFNGLPROGRAMUNIFORMHANDLEUI64ARBPROC glad_glProgramUniformHandleui64ARB; +#define glProgramUniformHandleui64ARB glad_glProgramUniformHandleui64ARB +typedef void (APIENTRYP PFNGLPROGRAMUNIFORMHANDLEUI64VARBPROC)(GLuint program, GLint location, GLsizei count, const GLuint64 *values); +GLAPI PFNGLPROGRAMUNIFORMHANDLEUI64VARBPROC glad_glProgramUniformHandleui64vARB; +#define glProgramUniformHandleui64vARB glad_glProgramUniformHandleui64vARB +typedef GLboolean (APIENTRYP PFNGLISTEXTUREHANDLERESIDENTARBPROC)(GLuint64 handle); +GLAPI PFNGLISTEXTUREHANDLERESIDENTARBPROC glad_glIsTextureHandleResidentARB; +#define glIsTextureHandleResidentARB glad_glIsTextureHandleResidentARB +typedef GLboolean (APIENTRYP PFNGLISIMAGEHANDLERESIDENTARBPROC)(GLuint64 handle); +GLAPI PFNGLISIMAGEHANDLERESIDENTARBPROC glad_glIsImageHandleResidentARB; +#define glIsImageHandleResidentARB glad_glIsImageHandleResidentARB +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64ARBPROC)(GLuint index, GLuint64EXT x); +GLAPI PFNGLVERTEXATTRIBL1UI64ARBPROC glad_glVertexAttribL1ui64ARB; +#define glVertexAttribL1ui64ARB glad_glVertexAttribL1ui64ARB +typedef void (APIENTRYP PFNGLVERTEXATTRIBL1UI64VARBPROC)(GLuint index, const GLuint64EXT *v); +GLAPI PFNGLVERTEXATTRIBL1UI64VARBPROC glad_glVertexAttribL1ui64vARB; +#define glVertexAttribL1ui64vARB glad_glVertexAttribL1ui64vARB +typedef void (APIENTRYP PFNGLGETVERTEXATTRIBLUI64VARBPROC)(GLuint index, GLenum pname, GLuint64EXT *params); +GLAPI PFNGLGETVERTEXATTRIBLUI64VARBPROC glad_glGetVertexAttribLui64vARB; +#define glGetVertexAttribLui64vARB glad_glGetVertexAttribLui64vARB +#endif +#ifndef GL_ARB_texture_filter_anisotropic +#define GL_ARB_texture_filter_anisotropic 1 +GLAPI int GLAD_GL_ARB_texture_filter_anisotropic; +#endif #ifdef __cplusplus } diff --git a/third-party/glad/src/glad.c b/third-party/glad/src/glad.c index 9b922f202..bce576bda 100644 --- a/third-party/glad/src/glad.c +++ b/third-party/glad/src/glad.c @@ -1,22 +1,23 @@ /* - OpenGL loader generated by glad 0.1.34 on Wed Oct 6 20:57:47 2021. + OpenGL loader generated by glad 0.1.34 on Sun Nov 14 17:13:20 2021. Language/Generator: C/C++ Specification: gl APIs: gl=4.3 Profile: compatibility Extensions: - + GL_ARB_bindless_texture, + GL_ARB_texture_filter_anisotropic Loader: True Local files: False Omit khrplatform: False Reproducible: False Commandline: - --profile="compatibility" --api="gl=4.3" --generator="c" --spec="gl" --extensions="" + --profile="compatibility" --api="gl=4.3" --generator="c" --spec="gl" --extensions="GL_ARB_bindless_texture,GL_ARB_texture_filter_anisotropic" Online: - https://glad.dav1d.de/#profile=compatibility&language=c&specification=gl&loader=on&api=gl%3D4.3 + https://glad.dav1d.de/#profile=compatibility&language=c&specification=gl&loader=on&api=gl%3D4.3&extensions=GL_ARB_bindless_texture&extensions=GL_ARB_texture_filter_anisotropic */ #include @@ -1184,6 +1185,24 @@ PFNGLWINDOWPOS3IPROC glad_glWindowPos3i = NULL; PFNGLWINDOWPOS3IVPROC glad_glWindowPos3iv = NULL; PFNGLWINDOWPOS3SPROC glad_glWindowPos3s = NULL; PFNGLWINDOWPOS3SVPROC glad_glWindowPos3sv = NULL; +int GLAD_GL_ARB_bindless_texture = 0; +int GLAD_GL_ARB_texture_filter_anisotropic = 0; +PFNGLGETTEXTUREHANDLEARBPROC glad_glGetTextureHandleARB = NULL; +PFNGLGETTEXTURESAMPLERHANDLEARBPROC glad_glGetTextureSamplerHandleARB = NULL; +PFNGLMAKETEXTUREHANDLERESIDENTARBPROC glad_glMakeTextureHandleResidentARB = NULL; +PFNGLMAKETEXTUREHANDLENONRESIDENTARBPROC glad_glMakeTextureHandleNonResidentARB = NULL; +PFNGLGETIMAGEHANDLEARBPROC glad_glGetImageHandleARB = NULL; +PFNGLMAKEIMAGEHANDLERESIDENTARBPROC glad_glMakeImageHandleResidentARB = NULL; +PFNGLMAKEIMAGEHANDLENONRESIDENTARBPROC glad_glMakeImageHandleNonResidentARB = NULL; +PFNGLUNIFORMHANDLEUI64ARBPROC glad_glUniformHandleui64ARB = NULL; +PFNGLUNIFORMHANDLEUI64VARBPROC glad_glUniformHandleui64vARB = NULL; +PFNGLPROGRAMUNIFORMHANDLEUI64ARBPROC glad_glProgramUniformHandleui64ARB = NULL; +PFNGLPROGRAMUNIFORMHANDLEUI64VARBPROC glad_glProgramUniformHandleui64vARB = NULL; +PFNGLISTEXTUREHANDLERESIDENTARBPROC glad_glIsTextureHandleResidentARB = NULL; +PFNGLISIMAGEHANDLERESIDENTARBPROC glad_glIsImageHandleResidentARB = NULL; +PFNGLVERTEXATTRIBL1UI64ARBPROC glad_glVertexAttribL1ui64ARB = NULL; +PFNGLVERTEXATTRIBL1UI64VARBPROC glad_glVertexAttribL1ui64vARB = NULL; +PFNGLGETVERTEXATTRIBLUI64VARBPROC glad_glGetVertexAttribLui64vARB = NULL; static void load_GL_VERSION_1_0(GLADloadproc load) { if(!GLAD_GL_VERSION_1_0) return; glad_glCullFace = (PFNGLCULLFACEPROC)load("glCullFace"); @@ -2150,9 +2169,29 @@ static void load_GL_VERSION_4_3(GLADloadproc load) { glad_glGetObjectPtrLabel = (PFNGLGETOBJECTPTRLABELPROC)load("glGetObjectPtrLabel"); glad_glGetPointerv = (PFNGLGETPOINTERVPROC)load("glGetPointerv"); } +static void load_GL_ARB_bindless_texture(GLADloadproc load) { + if(!GLAD_GL_ARB_bindless_texture) return; + glad_glGetTextureHandleARB = (PFNGLGETTEXTUREHANDLEARBPROC)load("glGetTextureHandleARB"); + glad_glGetTextureSamplerHandleARB = (PFNGLGETTEXTURESAMPLERHANDLEARBPROC)load("glGetTextureSamplerHandleARB"); + glad_glMakeTextureHandleResidentARB = (PFNGLMAKETEXTUREHANDLERESIDENTARBPROC)load("glMakeTextureHandleResidentARB"); + glad_glMakeTextureHandleNonResidentARB = (PFNGLMAKETEXTUREHANDLENONRESIDENTARBPROC)load("glMakeTextureHandleNonResidentARB"); + glad_glGetImageHandleARB = (PFNGLGETIMAGEHANDLEARBPROC)load("glGetImageHandleARB"); + glad_glMakeImageHandleResidentARB = (PFNGLMAKEIMAGEHANDLERESIDENTARBPROC)load("glMakeImageHandleResidentARB"); + glad_glMakeImageHandleNonResidentARB = (PFNGLMAKEIMAGEHANDLENONRESIDENTARBPROC)load("glMakeImageHandleNonResidentARB"); + glad_glUniformHandleui64ARB = (PFNGLUNIFORMHANDLEUI64ARBPROC)load("glUniformHandleui64ARB"); + glad_glUniformHandleui64vARB = (PFNGLUNIFORMHANDLEUI64VARBPROC)load("glUniformHandleui64vARB"); + glad_glProgramUniformHandleui64ARB = (PFNGLPROGRAMUNIFORMHANDLEUI64ARBPROC)load("glProgramUniformHandleui64ARB"); + glad_glProgramUniformHandleui64vARB = (PFNGLPROGRAMUNIFORMHANDLEUI64VARBPROC)load("glProgramUniformHandleui64vARB"); + glad_glIsTextureHandleResidentARB = (PFNGLISTEXTUREHANDLERESIDENTARBPROC)load("glIsTextureHandleResidentARB"); + glad_glIsImageHandleResidentARB = (PFNGLISIMAGEHANDLERESIDENTARBPROC)load("glIsImageHandleResidentARB"); + glad_glVertexAttribL1ui64ARB = (PFNGLVERTEXATTRIBL1UI64ARBPROC)load("glVertexAttribL1ui64ARB"); + glad_glVertexAttribL1ui64vARB = (PFNGLVERTEXATTRIBL1UI64VARBPROC)load("glVertexAttribL1ui64vARB"); + glad_glGetVertexAttribLui64vARB = (PFNGLGETVERTEXATTRIBLUI64VARBPROC)load("glGetVertexAttribLui64vARB"); +} static int find_extensionsGL(void) { if (!get_exts()) return 0; - (void)&has_ext; + GLAD_GL_ARB_bindless_texture = has_ext("GL_ARB_bindless_texture"); + GLAD_GL_ARB_texture_filter_anisotropic = has_ext("GL_ARB_texture_filter_anisotropic"); free_exts(); return 1; } @@ -2239,6 +2278,7 @@ int gladLoadGLLoader(GLADloadproc load) { load_GL_VERSION_4_3(load); if (!find_extensionsGL()) return 0; + load_GL_ARB_bindless_texture(load); return GLVersion.major != 0 || GLVersion.minor != 0; }