mirror of
https://github.com/open-goal/jak-project
synced 2026-07-04 05:20:56 -04:00
d3cc739e43
This attempts to get into master whatever work was done in this PR / it's earlier PR https://github.com/open-goal/jak-project/pull/3965 I don't want this work to be lost / floating around in massive PRs. However the changes are: - switch to ntsc_v1 instead of PAL as the development target, as we have done for all other games - remove most of the copied-from-jak2/3 changes as they need to be confirmed during the decompilation process not just assumed - avoids committing any changes to `game/kernel/common` as it was not clear to me if these were changes made in jak x's kernel that were not properly broken out into it's own functions. We don't want to accidentally introduce bugs into jak1-3's kernel code. - in other words, if the change in the kernel only happens in jak x...it should likely be specific to jak x's kernel, not common. --------- Co-authored-by: VodBox <dillon@vodbox.io> Co-authored-by: yodah <greenboyyodah@gmail.com>
1484 lines
52 KiB
C++
1484 lines
52 KiB
C++
#include "DirectRenderer.h"
|
|
|
|
#include "common/dma/gs.h"
|
|
#include "common/log/log.h"
|
|
#include "common/util/Assert.h"
|
|
|
|
#include "game/graphics/pipelines/opengl.h"
|
|
|
|
#include "fmt/format.h"
|
|
#include "third-party/imgui/imgui.h"
|
|
|
|
DirectRenderer::ScissorState DirectRenderer::m_scissor;
|
|
|
|
constexpr PerGameVersion<int> game_height(448, 416, 416, 416);
|
|
|
|
DirectRenderer::DirectRenderer(const std::string& name, int my_id, int batch_size)
|
|
: BucketRenderer(name, my_id), m_prim_buffer(batch_size) {
|
|
glGenBuffers(1, &m_ogl.vertex_buffer);
|
|
glGenVertexArrays(1, &m_ogl.vao);
|
|
glBindVertexArray(m_ogl.vao);
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer);
|
|
m_ogl.vertex_buffer_max_verts = batch_size * 3 * 2;
|
|
m_ogl.vertex_buffer_bytes = m_ogl.vertex_buffer_max_verts * sizeof(Vertex);
|
|
glBufferData(GL_ARRAY_BUFFER, m_ogl.vertex_buffer_bytes, nullptr,
|
|
GL_STREAM_DRAW); // todo stream?
|
|
glEnableVertexAttribArray(0);
|
|
glVertexAttribPointer(0, // location 0 in the shader
|
|
4, // 4 floats per vert (w unused)
|
|
GL_FLOAT, // floats
|
|
GL_TRUE, // normalized, ignored,
|
|
sizeof(Vertex), //
|
|
(void*)offsetof(Vertex, xyzf) // offset in array (why is this a pointer...)
|
|
);
|
|
|
|
glEnableVertexAttribArray(1);
|
|
glVertexAttribPointer(1, // location 0 in the shader
|
|
4, // 4 color components
|
|
GL_UNSIGNED_BYTE, // floats
|
|
GL_TRUE, // normalized, ignored,
|
|
sizeof(Vertex), //
|
|
(void*)offsetof(Vertex, rgba) // offset in array (why is this a pointer...)
|
|
);
|
|
|
|
glEnableVertexAttribArray(2);
|
|
glVertexAttribPointer(2, // location 0 in the shader
|
|
3, // 3 floats per vert
|
|
GL_FLOAT, // floats
|
|
GL_FALSE, // normalized, ignored,
|
|
sizeof(Vertex), //
|
|
(void*)offsetof(Vertex, stq) // offset in array (why is this a pointer...)
|
|
);
|
|
|
|
glEnableVertexAttribArray(3);
|
|
glVertexAttribIPointer(
|
|
3, // location 3 in the shader
|
|
4, // 3 floats per vert
|
|
GL_UNSIGNED_BYTE, // floats
|
|
sizeof(Vertex), //
|
|
(void*)offsetof(Vertex, tex_unit) // offset in array (why is this a pointer...)
|
|
);
|
|
|
|
glEnableVertexAttribArray(4);
|
|
glVertexAttribIPointer(
|
|
4, // location 4 in the shader
|
|
1, // 3 floats per vert
|
|
GL_UNSIGNED_BYTE, // floats
|
|
sizeof(Vertex), //
|
|
(void*)offsetof(Vertex, use_uv) // offset in array (why is this a pointer...)
|
|
);
|
|
|
|
glEnableVertexAttribArray(5);
|
|
glVertexAttribPointer(5, // location 5 in the shader
|
|
4, // 4 floats per vert
|
|
GL_FLOAT, // floats
|
|
GL_FALSE, // normalized, ignored,
|
|
sizeof(Vertex), //
|
|
(void*)offsetof(Vertex, scissor) // offset in array
|
|
);
|
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
|
glBindVertexArray(0);
|
|
}
|
|
|
|
DirectRenderer::~DirectRenderer() {
|
|
glDeleteBuffers(1, &m_ogl.vertex_buffer);
|
|
glDeleteVertexArrays(1, &m_ogl.vao);
|
|
}
|
|
|
|
/*!
|
|
* Render from a DMA bucket.
|
|
*/
|
|
void DirectRenderer::render(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
pre_render();
|
|
// if we're rendering from a bucket, we should start off we a totally reset state:
|
|
reset_state();
|
|
|
|
// just dump the DMA data into the other the render function
|
|
while (dma.current_tag_offset() != render_state->next_bucket) {
|
|
auto data = dma.read_and_advance();
|
|
if (data.size_bytes && m_enabled) {
|
|
render_vif(data.vif0(), data.vif1(), data.data, data.size_bytes, render_state, prof);
|
|
}
|
|
|
|
if (dma.current_tag_offset() == render_state->default_regs_buffer) {
|
|
// reset_state();
|
|
dma.read_and_advance(); // cnt
|
|
ASSERT(dma.current_tag().kind == DmaTag::Kind::RET);
|
|
dma.read_and_advance(); // ret
|
|
}
|
|
}
|
|
|
|
if (m_enabled) {
|
|
flush_pending(render_state, prof);
|
|
}
|
|
post_render();
|
|
glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
}
|
|
|
|
void DirectRenderer::reset_state() {
|
|
m_test_state_needs_gl_update = true;
|
|
m_test_state = TestState();
|
|
|
|
m_blend_state_needs_gl_update = true;
|
|
m_blend_state = BlendState();
|
|
|
|
m_prim_gl_state_needs_gl_update = true;
|
|
m_prim_gl_state = PrimGlState();
|
|
|
|
for (int i = 0; i < TEXTURE_STATE_COUNT; ++i) {
|
|
m_buffered_tex_state[i] = TextureState();
|
|
}
|
|
m_tex_state_from_reg = {};
|
|
m_next_free_tex_state = 0;
|
|
m_current_tex_state_idx = -1;
|
|
|
|
m_prim_building = PrimBuildState();
|
|
|
|
m_stats = {};
|
|
}
|
|
|
|
void DirectRenderer::init_shaders(ShaderLibrary& sl) {
|
|
auto id = sl[ShaderId::DIRECT_BASIC_TEXTURED].id();
|
|
m_uniforms.alpha_min = glGetUniformLocation(id, "alpha_min");
|
|
m_uniforms.alpha_max = glGetUniformLocation(id, "alpha_max");
|
|
m_uniforms.normal_shader_id = id;
|
|
}
|
|
|
|
void DirectRenderer::draw_debug_window() {
|
|
ImGui::Checkbox("Wireframe", &m_debug_state.wireframe);
|
|
ImGui::SameLine();
|
|
ImGui::Checkbox("No-texture", &m_debug_state.disable_texture);
|
|
ImGui::SameLine();
|
|
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);
|
|
|
|
ImGui::Text("Triangles: %d", m_stats.triangles);
|
|
ImGui::SameLine();
|
|
ImGui::Text("Draws: %d", m_stats.draw_calls);
|
|
|
|
ImGui::Text("Flush from state change:");
|
|
ImGui::Text(" tex0: %d", m_stats.flush_from_tex_0);
|
|
ImGui::Text(" tex1: %d", m_stats.flush_from_tex_1);
|
|
ImGui::Text(" zbuf: %d", m_stats.flush_from_zbuf);
|
|
ImGui::Text(" test: %d", m_stats.flush_from_test);
|
|
ImGui::Text(" ta0: %d", m_stats.flush_from_ta0);
|
|
ImGui::Text(" alph: %d", m_stats.flush_from_alpha);
|
|
ImGui::Text(" clmp: %d", m_stats.flush_from_clamp);
|
|
ImGui::Text(" prim: %d", m_stats.flush_from_prim);
|
|
ImGui::Text(" texstate: %d", m_stats.flush_from_state_exhaust);
|
|
ImGui::Text(" Total: %d/%d",
|
|
m_stats.flush_from_prim + m_stats.flush_from_clamp + m_stats.flush_from_alpha +
|
|
m_stats.flush_from_test + m_stats.flush_from_zbuf + m_stats.flush_from_tex_1 +
|
|
m_stats.flush_from_tex_0 + m_stats.flush_from_state_exhaust,
|
|
m_stats.draw_calls);
|
|
}
|
|
|
|
float u32_to_float(u32 in) {
|
|
double x = (double)in / UINT32_MAX;
|
|
return x;
|
|
}
|
|
|
|
float u32_to_sc(u32 in) {
|
|
float flt = u32_to_float(in);
|
|
return (flt - 0.5) * 16.0;
|
|
}
|
|
|
|
void DirectRenderer::lookup_textures_again(SharedRenderState* render_state) {
|
|
for (int i = 0; i < TEXTURE_STATE_COUNT; i++) {
|
|
if (m_buffered_tex_state_currently_bound[i]) {
|
|
auto& tex_state = m_buffered_tex_state[i];
|
|
tex_state.used = true;
|
|
update_gl_texture(render_state, i);
|
|
tex_state.used = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void DirectRenderer::flush_pending(SharedRenderState* render_state, ScopedProfilerNode& prof) {
|
|
// update opengl state
|
|
if (m_blend_state_needs_gl_update) {
|
|
update_gl_blend();
|
|
m_blend_state_needs_gl_update = false;
|
|
}
|
|
|
|
if (m_prim_gl_state_needs_gl_update) {
|
|
update_gl_prim(render_state);
|
|
m_prim_gl_state_needs_gl_update = false;
|
|
}
|
|
|
|
if (m_test_state_needs_gl_update) {
|
|
update_gl_test();
|
|
m_test_state_needs_gl_update = false;
|
|
}
|
|
|
|
for (int i = 0; i < TEXTURE_STATE_COUNT; i++) {
|
|
auto& tex_state = m_buffered_tex_state[i];
|
|
if (tex_state.used) {
|
|
update_gl_texture(render_state, i);
|
|
tex_state.used = false;
|
|
m_buffered_tex_state_currently_bound[i] = true;
|
|
} else {
|
|
m_buffered_tex_state_currently_bound[i] = false;
|
|
}
|
|
}
|
|
m_next_free_tex_state = 0;
|
|
m_current_tex_state_idx = -1;
|
|
|
|
// NOTE: sometimes we want to update the GL state without actually rendering anything, such as sky
|
|
// textures, so we only return after we've updated the full state
|
|
if (m_prim_buffer.vert_count == 0) {
|
|
return;
|
|
}
|
|
|
|
if (m_debug_state.disable_texture) {
|
|
// a bit of a hack, this forces the non-textured shader always.
|
|
render_state->shaders[ShaderId::DIRECT_BASIC].activate();
|
|
m_blend_state_needs_gl_update = true;
|
|
m_prim_gl_state_needs_gl_update = true;
|
|
}
|
|
|
|
if (m_debug_state.red) {
|
|
render_state->shaders[ShaderId::DEBUG_RED].activate();
|
|
glDisable(GL_BLEND);
|
|
m_prim_gl_state_needs_gl_update = true;
|
|
m_blend_state_needs_gl_update = true;
|
|
}
|
|
|
|
// hacks
|
|
if (m_debug_state.always_draw) {
|
|
glDisable(GL_DEPTH_TEST);
|
|
glDepthFunc(GL_ALWAYS);
|
|
}
|
|
|
|
glBindVertexArray(m_ogl.vao);
|
|
// render!
|
|
// update buffers:
|
|
glBindBuffer(GL_ARRAY_BUFFER, m_ogl.vertex_buffer);
|
|
glBufferData(GL_ARRAY_BUFFER, m_prim_buffer.vert_count * sizeof(Vertex),
|
|
m_prim_buffer.vertices.data(), GL_STREAM_DRAW);
|
|
|
|
GLint current_shader;
|
|
GLint viewport_size[4];
|
|
glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_shader);
|
|
glGetIntegerv(GL_VIEWPORT, viewport_size);
|
|
glUniform1i(glGetUniformLocation(current_shader, "scissor_enable"),
|
|
m_scissor_enable && !m_offscreen_mode);
|
|
glUniform4f(glGetUniformLocation(current_shader, "game_sizes"), 512.0f,
|
|
game_height[render_state->version], viewport_size[2], viewport_size[3]);
|
|
|
|
int draw_count = 0;
|
|
int num_tris = 0;
|
|
|
|
if (m_test_state_needs_double_draw && current_shader == m_uniforms.normal_shader_id) {
|
|
// this batch thing is a hack to make the sky in jak 2 draw correctly.
|
|
// This is the usual atest with FB_ONLY issue.
|
|
// we should check what pcsx2 does
|
|
int n_batch = m_prim_buffer.vert_count;
|
|
if (n_batch > 50 && n_batch < 700 && (n_batch % 2) == 0) {
|
|
n_batch = n_batch / 2;
|
|
} else {
|
|
// printf("not splitting batch %d\n", n_batch);
|
|
}
|
|
int offset = 0;
|
|
while (offset < m_prim_buffer.vert_count) {
|
|
glDepthMask(GL_TRUE);
|
|
glUniform1f(m_uniforms.alpha_min, m_double_draw_aref);
|
|
glUniform1f(m_uniforms.alpha_max, 10);
|
|
glDrawArrays(GL_TRIANGLES, offset, n_batch);
|
|
glDepthMask(GL_FALSE);
|
|
glUniform1f(m_uniforms.alpha_min, -10);
|
|
glUniform1f(m_uniforms.alpha_max, m_double_draw_aref);
|
|
glDrawArrays(GL_TRIANGLES, offset, n_batch);
|
|
offset += n_batch;
|
|
draw_count += 2;
|
|
num_tris += n_batch / 3;
|
|
}
|
|
m_test_state_needs_double_draw = false;
|
|
m_test_state_needs_gl_update = true;
|
|
m_prim_gl_state_needs_gl_update = true;
|
|
} else {
|
|
glDrawArrays(GL_TRIANGLES, 0, m_prim_buffer.vert_count);
|
|
num_tris += m_prim_buffer.vert_count / 3;
|
|
draw_count++;
|
|
}
|
|
|
|
if (m_debug_state.wireframe) {
|
|
render_state->shaders[ShaderId::DEBUG_RED].activate();
|
|
glDisable(GL_BLEND);
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
|
glDrawArrays(GL_TRIANGLES, 0, m_prim_buffer.vert_count);
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
m_blend_state_needs_gl_update = true;
|
|
m_prim_gl_state_needs_gl_update = true;
|
|
draw_count++;
|
|
}
|
|
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindVertexArray(0);
|
|
prof.add_tri(num_tris);
|
|
prof.add_draw_call(draw_count);
|
|
m_stats.triangles += num_tris;
|
|
m_stats.draw_calls += draw_count;
|
|
m_prim_buffer.vert_count = 0;
|
|
}
|
|
|
|
void DirectRenderer::update_gl_prim(SharedRenderState* render_state) {
|
|
// currently gouraud is handled in setup.
|
|
const auto& state = m_prim_gl_state;
|
|
if (state.texture_enable) {
|
|
float alpha_min = 0.0;
|
|
float alpha_max = 10;
|
|
int greater = 0;
|
|
if (m_test_state.alpha_test_enable) {
|
|
switch (m_test_state.alpha_test) {
|
|
case GsTest::AlphaTest::ALWAYS:
|
|
break;
|
|
case GsTest::AlphaTest::GEQUAL:
|
|
alpha_min = m_test_state.aref / 128.f;
|
|
m_double_draw_aref = alpha_min;
|
|
greater = 0;
|
|
break;
|
|
case GsTest::AlphaTest::GREATER:
|
|
alpha_min = (1 + m_test_state.aref) / 128.f;
|
|
m_double_draw_aref = alpha_min;
|
|
greater = 1;
|
|
break;
|
|
case GsTest::AlphaTest::NEVER:
|
|
break;
|
|
default:
|
|
ASSERT_MSG(false, fmt::format("unknown alpha test: {}", (int)m_test_state.alpha_test));
|
|
}
|
|
}
|
|
|
|
render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].activate();
|
|
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].id(),
|
|
"alpha_min"),
|
|
alpha_min);
|
|
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].id(),
|
|
"alpha_max"),
|
|
alpha_max);
|
|
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].id(),
|
|
"color_mult"),
|
|
m_ogl.color_mult);
|
|
glUniform1f(glGetUniformLocation(render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].id(),
|
|
"alpha_mult"),
|
|
m_ogl.alpha_mult);
|
|
glUniform4f(glGetUniformLocation(render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].id(),
|
|
"fog_color"),
|
|
render_state->fog_color[0] / 255.f, render_state->fog_color[1] / 255.f,
|
|
render_state->fog_color[2] / 255.f, render_state->fog_intensity / 255);
|
|
glUniform1i(glGetUniformLocation(render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].id(),
|
|
"offscreen_mode"),
|
|
m_offscreen_mode);
|
|
glUniform1i(glGetUniformLocation(render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].id(),
|
|
"greater"),
|
|
greater);
|
|
glUniform1f(
|
|
glGetUniformLocation(render_state->shaders[ShaderId::DIRECT_BASIC_TEXTURED].id(), "ta0"),
|
|
state.ta0 / 255.f);
|
|
|
|
} else {
|
|
render_state->shaders[ShaderId::DIRECT_BASIC].activate();
|
|
}
|
|
|
|
if (state.aa_enable) {
|
|
ASSERT(false);
|
|
}
|
|
if (state.ctxt) {
|
|
ASSERT(false);
|
|
}
|
|
if (state.fix) {
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
|
|
void DirectRenderer::update_gl_texture(SharedRenderState* render_state, int unit) {
|
|
std::optional<u64> tex;
|
|
auto& state = m_buffered_tex_state[unit];
|
|
if (!state.used) {
|
|
// nothing used this state, don't bother binding the texture.
|
|
return;
|
|
}
|
|
if (state.using_mt4hh) {
|
|
tex = render_state->texture_pool->lookup_mt4hh(state.texture_base_ptr);
|
|
} else {
|
|
tex = render_state->texture_pool->lookup(state.texture_base_ptr);
|
|
}
|
|
|
|
if (!tex) {
|
|
lg::warn("Failed to find texture at {}, using random (direct: {})", state.texture_base_ptr,
|
|
name_and_id());
|
|
tex = render_state->texture_pool->get_placeholder_texture();
|
|
}
|
|
ASSERT(tex);
|
|
|
|
glActiveTexture(GL_TEXTURE20 + unit);
|
|
glBindTexture(GL_TEXTURE_2D, *tex);
|
|
// Note: CLAMP and CLAMP_TO_EDGE are different...
|
|
if (state.m_clamp_state.clamp_s) {
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
} else {
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
|
|
}
|
|
|
|
if (state.m_clamp_state.clamp_t) {
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
} else {
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
|
}
|
|
|
|
if (state.enable_tex_filt) {
|
|
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);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
}
|
|
}
|
|
|
|
void DirectRenderer::update_gl_blend() {
|
|
const auto& state = m_blend_state;
|
|
m_ogl.color_mult = 1.f;
|
|
m_ogl.alpha_mult = 1.f;
|
|
m_prim_gl_state_needs_gl_update = true;
|
|
if (!state.alpha_blend_enable) {
|
|
glDisable(GL_BLEND);
|
|
} else {
|
|
glEnable(GL_BLEND);
|
|
glBlendColor(1, 1, 1, 1);
|
|
|
|
if (state.a == GsAlpha::BlendMode::SOURCE && state.b == GsAlpha::BlendMode::DEST &&
|
|
state.c == GsAlpha::BlendMode::SOURCE && state.d == GsAlpha::BlendMode::DEST) {
|
|
// (Cs - Cd) * As + Cd
|
|
// Cs * As + (1 - As) * Cd
|
|
// s, d
|
|
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
|
|
glBlendEquation(GL_FUNC_ADD);
|
|
|
|
} else if (state.a == GsAlpha::BlendMode::SOURCE &&
|
|
state.b == GsAlpha::BlendMode::ZERO_OR_FIXED &&
|
|
state.c == GsAlpha::BlendMode::SOURCE && state.d == GsAlpha::BlendMode::DEST) {
|
|
// (Cs - 0) * As + Cd
|
|
// Cs * As + (1) * Cd
|
|
// s, d
|
|
ASSERT(state.fix == 0);
|
|
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ZERO);
|
|
glBlendEquation(GL_FUNC_ADD);
|
|
} else if (state.a == GsAlpha::BlendMode::ZERO_OR_FIXED &&
|
|
state.b == GsAlpha::BlendMode::SOURCE && state.c == GsAlpha::BlendMode::SOURCE &&
|
|
state.d == GsAlpha::BlendMode::DEST) {
|
|
// (0 - Cs) * As + Cd
|
|
// Cd - Cs * As
|
|
// s, d
|
|
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE, GL_ONE, GL_ZERO);
|
|
glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
|
|
} else if (state.a == GsAlpha::BlendMode::SOURCE && state.b == GsAlpha::BlendMode::DEST &&
|
|
state.c == GsAlpha::BlendMode::ZERO_OR_FIXED &&
|
|
state.d == GsAlpha::BlendMode::DEST) {
|
|
// (Cs - Cd) * fix + Cd
|
|
// Cs * fix + (1 - fx) * Cd
|
|
glBlendFuncSeparate(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA, GL_ONE, GL_ZERO);
|
|
glBlendColor(0, 0, 0, state.fix / 127.f);
|
|
glBlendEquation(GL_FUNC_ADD);
|
|
} else if (state.a == GsAlpha::BlendMode::SOURCE && state.b == GsAlpha::BlendMode::SOURCE &&
|
|
state.c == GsAlpha::BlendMode::SOURCE && state.d == GsAlpha::BlendMode::SOURCE) {
|
|
// trick to disable alpha blending.
|
|
glDisable(GL_BLEND);
|
|
} else if (state.a == GsAlpha::BlendMode::SOURCE &&
|
|
state.b == GsAlpha::BlendMode::ZERO_OR_FIXED &&
|
|
state.c == GsAlpha::BlendMode::DEST && state.d == GsAlpha::BlendMode::DEST) {
|
|
// (Cs - 0) * Ad + Cd
|
|
glBlendFuncSeparate(GL_DST_ALPHA, GL_ONE, GL_ONE, GL_ZERO);
|
|
glBlendEquation(GL_FUNC_ADD);
|
|
m_ogl.color_mult = 0.5;
|
|
} else {
|
|
// unsupported blend: a 0 b 2 c 2 d 1
|
|
lg::error("unsupported blend (direct): a {} b {} c {} d {}", (int)state.a, (int)state.b,
|
|
(int)state.c, (int)state.d);
|
|
// ASSERT(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void DirectRenderer::update_gl_test() {
|
|
const auto& state = m_test_state;
|
|
|
|
if (state.zte) {
|
|
glEnable(GL_DEPTH_TEST);
|
|
switch (state.ztst) {
|
|
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 {
|
|
// you aren't supposed to turn off z test enable, the GS had some bugs
|
|
ASSERT(false);
|
|
}
|
|
|
|
if (state.date) {
|
|
ASSERT(false);
|
|
}
|
|
|
|
bool alpha_trick_to_disable = m_test_state.alpha_test_enable &&
|
|
m_test_state.alpha_test == GsTest::AlphaTest::NEVER &&
|
|
m_test_state.afail == GsTest::AlphaFail::FB_ONLY;
|
|
|
|
if (m_test_state.afail == GsTest::AlphaFail::FB_ONLY ||
|
|
m_test_state.afail == GsTest::AlphaFail::RGB_ONLY) {
|
|
m_test_state_needs_double_draw = true;
|
|
} else {
|
|
m_test_state_needs_double_draw = false;
|
|
}
|
|
|
|
if (state.depth_writes && !alpha_trick_to_disable) {
|
|
glDepthMask(GL_TRUE);
|
|
} else {
|
|
glDepthMask(GL_FALSE);
|
|
}
|
|
|
|
if (state.write_rgb) {
|
|
glColorMaski(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
|
} else {
|
|
glColorMaski(0, GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
/*!
|
|
* If it's a direct, returns the qwc.
|
|
* If it's ignorable (nop, flush), returns 0.
|
|
* Otherwise, assert.
|
|
*/
|
|
u32 get_direct_qwc_or_nop(const VifCode& code) {
|
|
switch (code.kind) {
|
|
case VifCode::Kind::NOP:
|
|
case VifCode::Kind::FLUSHA:
|
|
return 0;
|
|
case VifCode::Kind::DIRECT:
|
|
if (code.immediate == 0) {
|
|
return 65536;
|
|
} else {
|
|
return code.immediate;
|
|
}
|
|
default:
|
|
printf("expected direct, got %s\n", code.print().c_str());
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
/*!
|
|
* Render VIF data.
|
|
*/
|
|
void DirectRenderer::render_vif(u32 vif0,
|
|
u32 vif1,
|
|
const u8* data,
|
|
u32 size,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
// here we process VIF data. Basically we just go forward, looking for DIRECTs.
|
|
// We skip stuff like flush and nops.
|
|
|
|
// read the vif cmds at the front.
|
|
u32 gif_qwc = get_direct_qwc_or_nop(VifCode(vif0));
|
|
if (gif_qwc) {
|
|
// we got a direct. expect the second thing to be a nop/similar.
|
|
ASSERT(get_direct_qwc_or_nop(VifCode(vif1)) == 0);
|
|
} else {
|
|
gif_qwc = get_direct_qwc_or_nop(VifCode(vif1));
|
|
}
|
|
|
|
u32 offset_into_data = 0;
|
|
while (offset_into_data < size) {
|
|
if (gif_qwc) {
|
|
if (offset_into_data & 0xf) {
|
|
// not aligned. should get nops.
|
|
u32 vif;
|
|
memcpy(&vif, data + offset_into_data, 4);
|
|
offset_into_data += 4;
|
|
ASSERT(get_direct_qwc_or_nop(VifCode(vif)) == 0);
|
|
} else {
|
|
// aligned! do a gif transfer!
|
|
render_gif(data + offset_into_data, gif_qwc * 16, render_state, prof);
|
|
offset_into_data += gif_qwc * 16;
|
|
}
|
|
} else {
|
|
// we are reading VIF data.
|
|
u32 vif;
|
|
memcpy(&vif, data + offset_into_data, 4);
|
|
offset_into_data += 4;
|
|
gif_qwc = get_direct_qwc_or_nop(VifCode(vif));
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Render GIF data.
|
|
*/
|
|
void DirectRenderer::render_gif(const u8* data,
|
|
u32 size,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
if (size != UINT32_MAX) {
|
|
ASSERT(size >= 16);
|
|
}
|
|
|
|
if (m_blit_buf_state.expect == 5) {
|
|
ASSERT(m_blit_buf_state.qwc * 16 == size);
|
|
m_blit_buf_state.expect = 0;
|
|
return;
|
|
}
|
|
|
|
bool eop = false;
|
|
|
|
u32 offset = 0;
|
|
while (!eop) {
|
|
if (size != UINT32_MAX) {
|
|
ASSERT(offset < size);
|
|
}
|
|
GifTag tag(data + offset);
|
|
offset += 16;
|
|
eop = tag.eop();
|
|
|
|
// 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(), render_state, prof);
|
|
}
|
|
for (u32 loop = 0; loop < tag.nloop(); loop++) {
|
|
for (u32 reg = 0; reg < nreg; reg++) {
|
|
// fmt::print("{}\n", reg_descriptor_name(reg_desc[reg]));
|
|
switch (reg_desc[reg]) {
|
|
case GifTag::RegisterDescriptor::AD:
|
|
handle_ad(data + offset, render_state, prof);
|
|
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, render_state, prof);
|
|
break;
|
|
case GifTag::RegisterDescriptor::XYZ2:
|
|
handle_xyz2_packed(data + offset, render_state, prof);
|
|
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);
|
|
break;
|
|
case GifTag::RegisterDescriptor::UV:
|
|
handle_uv_packed(data + offset);
|
|
break;
|
|
default:
|
|
ASSERT_MSG(false, fmt::format("Register {} is not supported in packed mode yet\n",
|
|
reg_descriptor_name(reg_desc[reg])));
|
|
}
|
|
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:
|
|
ASSERT_MSG(false, fmt::format("Register {} is not supported in reglist mode yet\n",
|
|
reg_descriptor_name(reg_desc[reg])));
|
|
}
|
|
offset += 8; // PACKED = quadwords
|
|
}
|
|
}
|
|
} else if (format == GifTag::Format::IMAGE) {
|
|
ASSERT(m_blit_buf_state.expect == 4);
|
|
m_blit_buf_state.expect++;
|
|
|
|
// dont support non-eop image transfers yet
|
|
ASSERT(eop);
|
|
// in IMAGE mode this is the amount of 2x64-bit (1 qword) we will transfer
|
|
m_blit_buf_state.qwc = tag.nloop();
|
|
} else {
|
|
ASSERT(false); // format not packed or reglist or image
|
|
}
|
|
}
|
|
|
|
if (size != UINT32_MAX) {
|
|
if ((offset + 15) / 16 != size / 16) {
|
|
ASSERT_MSG(false, fmt::format("DirectRenderer size failed in {}. expected: {}, got: {}",
|
|
name_and_id(), size, offset));
|
|
}
|
|
}
|
|
|
|
// fmt::print("{}\n", GifTag(data).print());
|
|
}
|
|
|
|
void DirectRenderer::handle_ad(const u8* data,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
u64 value;
|
|
GsRegisterAddress addr;
|
|
memcpy(&value, data, sizeof(u64));
|
|
memcpy(&addr, data + 8, sizeof(GsRegisterAddress));
|
|
|
|
// fmt::print("{}\n", register_address_name(addr));
|
|
switch (addr) {
|
|
case GsRegisterAddress::ZBUF_1:
|
|
handle_zbuf1(value, render_state, prof);
|
|
break;
|
|
case GsRegisterAddress::TEST_1:
|
|
handle_test1(value, render_state, prof);
|
|
break;
|
|
case GsRegisterAddress::ALPHA_1:
|
|
handle_alpha1(value, render_state, prof);
|
|
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, render_state, prof);
|
|
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:
|
|
// TODO this has the address of different mip levels.
|
|
break;
|
|
case GsRegisterAddress::TEXFLUSH:
|
|
break;
|
|
case GsRegisterAddress::FRAME_1:
|
|
handle_frame(value, render_state, prof);
|
|
break;
|
|
case GsRegisterAddress::RGBAQ: { // shadow scissor does this?
|
|
m_prim_building.rgba_reg[0] = data[0];
|
|
m_prim_building.rgba_reg[1] = data[1];
|
|
m_prim_building.rgba_reg[2] = data[2];
|
|
m_prim_building.rgba_reg[3] = data[3];
|
|
memcpy(&m_prim_building.Q, data + 4, 4);
|
|
} break;
|
|
case GsRegisterAddress::SCISSOR_1:
|
|
handle_scissor(value);
|
|
break;
|
|
case GsRegisterAddress::XYOFFSET_1:
|
|
ASSERT(render_state->version >= GameVersion::Jak2); // hardcoded jak 2 scissor vals in handle
|
|
handle_xyoffset(value);
|
|
break;
|
|
case GsRegisterAddress::COLCLAMP:
|
|
ASSERT(value == 1);
|
|
break;
|
|
case GsRegisterAddress::BITBLTBUF:
|
|
ASSERT(false);
|
|
handle_bitbltbuf(value);
|
|
break;
|
|
case GsRegisterAddress::TRXPOS:
|
|
ASSERT(false);
|
|
handle_trxpos(value);
|
|
break;
|
|
case GsRegisterAddress::TRXREG:
|
|
ASSERT(false);
|
|
handle_trxreg(value);
|
|
break;
|
|
case GsRegisterAddress::TRXDIR:
|
|
ASSERT(false);
|
|
handle_trxdir(value & 0x3, render_state, prof);
|
|
break;
|
|
default:
|
|
ASSERT_MSG(false, fmt::format("Address {} is not supported", register_address_name(addr)));
|
|
}
|
|
}
|
|
|
|
void DirectRenderer::handle_frame(u64, SharedRenderState*, ScopedProfilerNode&) {}
|
|
|
|
void DirectRenderer::handle_scissor(u64 val) {
|
|
m_scissor.scax0 = (val >> 0) & 0x7ff;
|
|
m_scissor.scax1 = (val >> 16) & 0x7ff;
|
|
m_scissor.scay0 = (val >> 32) & 0x7ff;
|
|
m_scissor.scay1 = (val >> 48) & 0x7ff;
|
|
m_scissor_enable = true;
|
|
}
|
|
|
|
void DirectRenderer::handle_xyoffset(u64 val) {
|
|
GsXYOffset xyo(val);
|
|
// :ofx #x7000 :ofy #x7300
|
|
float scale = -65536;
|
|
m_prim_buffer.x_off = scale * ((s32)xyo.ofx() - 0x7000) / float(UINT32_MAX);
|
|
m_prim_buffer.y_off = scale * ((s32)xyo.ofy() - 0x7300) / float(UINT32_MAX);
|
|
}
|
|
|
|
void DirectRenderer::handle_bitbltbuf(u64 val) {
|
|
ASSERT(m_blit_buf_state.expect == 0);
|
|
m_blit_buf_state.expect++;
|
|
|
|
m_blit_buf_state.sbp = (val >> 0) & 0x3fff;
|
|
m_blit_buf_state.sbw = (val >> 16) & 0x3f;
|
|
m_blit_buf_state.spsm = (val >> 24) & 0x3f;
|
|
m_blit_buf_state.dbp = (val >> 32) & 0x3fff;
|
|
m_blit_buf_state.dbw = (val >> 48) & 0x3f;
|
|
m_blit_buf_state.dpsm = (val >> 56) & 0x3f;
|
|
}
|
|
|
|
void DirectRenderer::handle_trxpos(u64 val) {
|
|
ASSERT(m_blit_buf_state.expect == 1);
|
|
m_blit_buf_state.expect++;
|
|
|
|
m_blit_buf_state.ssax = (val >> 0) & 0x7ff;
|
|
m_blit_buf_state.ssay = (val >> 16) & 0x7ff;
|
|
m_blit_buf_state.dsax = (val >> 32) & 0x7ff;
|
|
m_blit_buf_state.dsay = (val >> 48) & 0x7ff;
|
|
m_blit_buf_state.pixel_dir = (val >> 59) & 0x3;
|
|
}
|
|
|
|
void DirectRenderer::handle_trxreg(u64 val) {
|
|
ASSERT(m_blit_buf_state.expect == 2);
|
|
m_blit_buf_state.expect++;
|
|
|
|
m_blit_buf_state.width = (val >> 0) & 0xfff;
|
|
m_blit_buf_state.height = (val >> 32) & 0xfff;
|
|
}
|
|
|
|
void DirectRenderer::handle_trxdir(u64 dir,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& /*prof*/) {
|
|
ASSERT(m_blit_buf_state.expect == 3);
|
|
m_blit_buf_state.expect++;
|
|
|
|
auto get_tex_func = [&render_state](const std::string& name, u16 tbp) {
|
|
auto result = render_state->texture_pool->lookup(tbp);
|
|
if (!result) {
|
|
fmt::print("{} tbp {} not found\n", name, tbp);
|
|
} else {
|
|
fmt::print("{} tbp {} found\n", name, tbp);
|
|
}
|
|
return result;
|
|
};
|
|
fmt::print("GS TEXTURE COPY --\n");
|
|
fmt::print("src w/psm: {}/{} dst w/psm: {}/{}\n", m_blit_buf_state.sbw, m_blit_buf_state.spsm,
|
|
m_blit_buf_state.dbw, m_blit_buf_state.dpsm);
|
|
switch (dir) {
|
|
case 0: { // host->local
|
|
fmt::print("-- FROM EE\n");
|
|
auto dst_tex = get_tex_func("dst", m_blit_buf_state.dbp);
|
|
(void)dst_tex;
|
|
// ASSERT_MSG(false, "nyi trxdir host->local");
|
|
} break;
|
|
case 1: { // local->host
|
|
fmt::print("-- FROM GS\n");
|
|
auto src_tex = get_tex_func("src", m_blit_buf_state.sbp);
|
|
(void)src_tex;
|
|
// ASSERT_MSG(false, "nyi trxdir local->host");
|
|
} break;
|
|
case 2: { // local->local
|
|
fmt::print("-- GS <-> GS\n");
|
|
auto src_tex = get_tex_func("src", m_blit_buf_state.sbp);
|
|
auto dst_tex = get_tex_func("dst", m_blit_buf_state.dbp);
|
|
(void)src_tex;
|
|
(void)dst_tex;
|
|
} break;
|
|
case 3: // disable
|
|
fmt::print("-- HUH???\n");
|
|
ASSERT_MSG(false, "nyi trxdir disable");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void DirectRenderer::handle_tex1_1(u64 val) {
|
|
GsTex1 reg(val);
|
|
// for now, we aren't going to handle mipmapping. I don't think it's used with direct.
|
|
// ASSERT(reg.mxl() == 0);
|
|
// if that's true, we can ignore LCM, MTBA, L, K
|
|
|
|
bool want_tex_filt = reg.mmag();
|
|
if (want_tex_filt != m_tex_state_from_reg.enable_tex_filt) {
|
|
m_tex_state_from_reg.enable_tex_filt = want_tex_filt;
|
|
// we changed the state_from_reg, we no longer know if it points to a texture state.
|
|
m_current_tex_state_idx = -1;
|
|
}
|
|
|
|
// MMAG/MMIN specify texture filtering. For now, assume always linear
|
|
// ASSERT(reg.mmag() == true);
|
|
// if (!(reg.mmin() == 1 || reg.mmin() == 4)) { // with mipmap off, both of these are linear
|
|
// // lg::error("unsupported mmin");
|
|
// }
|
|
}
|
|
|
|
void DirectRenderer::handle_tex0_1_packed(const u8* data) {
|
|
u64 val;
|
|
memcpy(&val, data, sizeof(u64));
|
|
handle_tex0_1(val);
|
|
}
|
|
|
|
void DirectRenderer::handle_tex0_1(u64 val) {
|
|
GsTex0 reg(val);
|
|
// update tbp
|
|
if (m_tex_state_from_reg.current_register != reg) {
|
|
m_tex_state_from_reg.texture_base_ptr = reg.tbp0();
|
|
m_tex_state_from_reg.using_mt4hh = reg.psm() == GsTex0::PSM::PSMT4HH;
|
|
m_tex_state_from_reg.current_register = reg;
|
|
m_tex_state_from_reg.tcc = reg.tcc();
|
|
m_tex_state_from_reg.decal = reg.tfx() == GsTex0::TextureFunction::DECAL;
|
|
ASSERT(reg.tfx() == GsTex0::TextureFunction::DECAL ||
|
|
reg.tfx() == GsTex0::TextureFunction::MODULATE);
|
|
|
|
// we changed the state_from_reg, we no longer know if it points to a texture state.
|
|
m_current_tex_state_idx = -1;
|
|
}
|
|
|
|
// tbw: assume they got it right
|
|
// psm: assume they got it right
|
|
// tw: assume they got it right
|
|
// th: assume they got it right
|
|
|
|
// MERC hack
|
|
// ASSERT(reg.tfx() == GsTex0::TextureFunction::MODULATE);
|
|
|
|
// cbp: assume they got it right
|
|
// cpsm: assume they got it right
|
|
// csm: assume they got it right
|
|
}
|
|
|
|
void DirectRenderer::handle_st_packed(const u8* data) {
|
|
memcpy(&m_prim_building.st_reg.x(), data + 0, 4);
|
|
memcpy(&m_prim_building.st_reg.y(), data + 4, 4);
|
|
memcpy(&m_prim_building.Q, data + 8, 4);
|
|
}
|
|
|
|
void DirectRenderer::handle_uv_packed(const u8* data) {
|
|
u32 u, v;
|
|
memcpy(&u, data, 4);
|
|
memcpy(&v, data + 4, 4);
|
|
m_prim_building.st_reg.x() = u;
|
|
m_prim_building.st_reg.y() = v;
|
|
m_prim_building.Q = 1;
|
|
}
|
|
|
|
void DirectRenderer::handle_rgbaq_packed(const u8* data) {
|
|
// TODO update Q from st.
|
|
m_prim_building.rgba_reg[0] = data[0];
|
|
m_prim_building.rgba_reg[1] = data[4];
|
|
m_prim_building.rgba_reg[2] = data[8];
|
|
m_prim_building.rgba_reg[3] = data[12];
|
|
}
|
|
|
|
void DirectRenderer::handle_xyzf2_packed(const u8* data,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
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 << 16, y << 16, z, f, render_state, prof, !adc);
|
|
}
|
|
|
|
void DirectRenderer::handle_xyz2_packed(const u8* data,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
u32 x, y;
|
|
memcpy(&x, data, 4);
|
|
memcpy(&y, data + 4, 4);
|
|
|
|
u64 upper;
|
|
memcpy(&upper, data + 8, 8);
|
|
u32 z = upper;
|
|
|
|
bool adc = upper & (1ull << 47);
|
|
handle_xyzf2_common(x << 16, y << 16, z, 0, render_state, prof, !adc);
|
|
}
|
|
|
|
PerGameVersion<u32> normal_zbp = {448, 304, 304, 304};
|
|
void DirectRenderer::handle_zbuf1(u64 val,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
// note: we can basically ignore this. There's a single z buffer that's always configured the same
|
|
// way - 24-bit, at offset 448.
|
|
GsZbuf x(val);
|
|
ASSERT(x.psm() == TextureFormat::PSMZ24);
|
|
ASSERT(x.zbp() == normal_zbp[render_state->version]);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
void DirectRenderer::handle_test1(u64 val,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
GsTest reg(val);
|
|
if (reg.alpha_test_enable()) {
|
|
// ASSERT(reg.alpha_test() == GsTest::AlphaTest::ALWAYS);
|
|
}
|
|
ASSERT(!reg.date());
|
|
if (m_test_state.current_register != reg) {
|
|
m_stats.flush_from_test++;
|
|
flush_pending(render_state, prof);
|
|
m_test_state.from_register(reg);
|
|
m_test_state_needs_gl_update = true;
|
|
m_prim_gl_state_needs_gl_update = true;
|
|
}
|
|
}
|
|
void DirectRenderer::handle_texa(u64 val,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
GsTexa reg(val);
|
|
|
|
// rgba16 isn't used so this doesn't matter?
|
|
// but they use sane defaults anyway
|
|
// ASSERT(reg.ta0() == 0); TODO
|
|
if (m_prim_gl_state.ta0 != reg.ta0()) {
|
|
m_stats.flush_from_ta0++;
|
|
flush_pending(render_state, prof);
|
|
m_prim_gl_state.ta0 = reg.ta0();
|
|
m_test_state_needs_gl_update = true;
|
|
m_prim_gl_state_needs_gl_update = true;
|
|
m_blend_state_needs_gl_update = true;
|
|
}
|
|
ASSERT(reg.ta1() == 0x80); // note: check rgba16_to_rgba32 if this changes.
|
|
|
|
ASSERT(reg.aem() == false);
|
|
}
|
|
void DirectRenderer::handle_alpha1(u64 val,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
GsAlpha reg(val);
|
|
if (m_blend_state.current_register != reg) {
|
|
m_stats.flush_from_alpha++;
|
|
flush_pending(render_state, prof);
|
|
m_blend_state.from_register(reg);
|
|
m_blend_state_needs_gl_update = true;
|
|
}
|
|
}
|
|
|
|
void DirectRenderer::handle_pabe(u64 val) {
|
|
ASSERT(val == 0); // not really sure how to handle this yet.
|
|
}
|
|
|
|
void DirectRenderer::handle_clamp1(u64 val) {
|
|
if (!(val == 0b101 || val == 0 || val == 1 || val == 0b100)) {
|
|
// fmt::print("clamp: 0x{:x}\n", val);
|
|
// ASSERT(false);
|
|
}
|
|
|
|
if (m_tex_state_from_reg.m_clamp_state.current_register != val) {
|
|
m_current_tex_state_idx = -1;
|
|
m_tex_state_from_reg.m_clamp_state.current_register = val;
|
|
m_tex_state_from_reg.m_clamp_state.clamp_s = val & 0b001;
|
|
m_tex_state_from_reg.m_clamp_state.clamp_t = val & 0b100;
|
|
}
|
|
}
|
|
|
|
void DirectRenderer::handle_prim_packed(const u8* data,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
u64 val;
|
|
memcpy(&val, data, sizeof(u64));
|
|
handle_prim(val, render_state, prof);
|
|
}
|
|
|
|
void DirectRenderer::handle_prim(u64 val,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
if (m_prim_building.tri_strip_startup) {
|
|
m_prim_building.tri_strip_startup = 0;
|
|
m_prim_building.building_idx = 0;
|
|
} else {
|
|
if (m_prim_building.building_idx > 0) {
|
|
ASSERT(false); // shouldn't leave any half-finished prims
|
|
}
|
|
}
|
|
// need to flush any in progress prims to the buffer.
|
|
|
|
GsPrim prim(val);
|
|
if (m_prim_gl_state.current_register != prim || m_blend_state.alpha_blend_enable != prim.abe()) {
|
|
m_stats.flush_from_prim++;
|
|
flush_pending(render_state, prof);
|
|
m_prim_gl_state.from_register(prim);
|
|
m_blend_state.alpha_blend_enable = prim.abe();
|
|
m_prim_gl_state_needs_gl_update = true;
|
|
m_blend_state_needs_gl_update = true;
|
|
}
|
|
|
|
m_prim_building.kind = prim.kind();
|
|
}
|
|
|
|
void DirectRenderer::handle_rgbaq(u64 val) {
|
|
ASSERT((val >> 32) == 0); // q = 0
|
|
memcpy(m_prim_building.rgba_reg.data(), &val, 4);
|
|
}
|
|
|
|
int DirectRenderer::get_texture_unit_for_current_reg(SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
if (m_current_tex_state_idx != -1) {
|
|
return m_current_tex_state_idx;
|
|
}
|
|
|
|
if (m_next_free_tex_state >= TEXTURE_STATE_COUNT) {
|
|
m_stats.flush_from_state_exhaust++;
|
|
flush_pending(render_state, prof);
|
|
return get_texture_unit_for_current_reg(render_state, prof);
|
|
} else {
|
|
ASSERT(!m_buffered_tex_state[m_next_free_tex_state].used);
|
|
m_buffered_tex_state[m_next_free_tex_state] = m_tex_state_from_reg;
|
|
m_buffered_tex_state[m_next_free_tex_state].used = true;
|
|
m_current_tex_state_idx = m_next_free_tex_state++;
|
|
return m_current_tex_state_idx;
|
|
}
|
|
}
|
|
|
|
void DirectRenderer::handle_xyzf2_common(u32 x,
|
|
u32 y,
|
|
u32 z,
|
|
u8 f,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof,
|
|
bool advance) {
|
|
if (m_prim_buffer.is_full()) {
|
|
lg::warn("Buffer wrapped in {} ({} verts, {} bytes)", m_name, m_ogl.vertex_buffer_max_verts,
|
|
m_prim_buffer.vert_count * sizeof(Vertex));
|
|
flush_pending(render_state, prof);
|
|
}
|
|
|
|
m_prim_building.building_stq.at(m_prim_building.building_idx) = math::Vector<float, 3>(
|
|
m_prim_building.st_reg.x(), m_prim_building.st_reg.y(), m_prim_building.Q);
|
|
m_prim_building.building_rgba.at(m_prim_building.building_idx) = m_prim_building.rgba_reg;
|
|
m_prim_building.building_vert.at(m_prim_building.building_idx) = math::Vector<u32, 4>{x, y, z, f};
|
|
|
|
m_prim_building.building_idx++;
|
|
|
|
int tex_unit = get_texture_unit_for_current_reg(render_state, prof);
|
|
bool tcc = m_buffered_tex_state[tex_unit].tcc;
|
|
bool decal = m_buffered_tex_state[tex_unit].decal;
|
|
bool fge = m_prim_gl_state.fogging_enable;
|
|
bool use_uv = m_prim_gl_state.use_uv;
|
|
|
|
math::Vector<float, 4> scissor(m_scissor.scax0, m_scissor.scax1, m_scissor.scay0,
|
|
m_scissor.scay1);
|
|
|
|
switch (m_prim_building.kind) {
|
|
case GsPrim::Kind::SPRITE: {
|
|
if (m_prim_building.building_idx == 2) {
|
|
// build triangles from the sprite.
|
|
auto& corner1_vert = m_prim_building.building_vert[0];
|
|
auto& corner1_rgba = m_prim_building.building_rgba[0];
|
|
auto& corner2_vert = m_prim_building.building_vert[1];
|
|
auto& corner2_rgba = m_prim_building.building_rgba[1];
|
|
auto& corner1_stq = m_prim_building.building_stq[0];
|
|
auto& corner2_stq = m_prim_building.building_stq[1];
|
|
|
|
// should use most recent vertex z.
|
|
math::Vector<u32, 4> corner3_vert{corner1_vert[0], corner2_vert[1], corner2_vert[2], 0};
|
|
math::Vector<u32, 4> corner4_vert{corner2_vert[0], corner1_vert[1], corner2_vert[2], 0};
|
|
math::Vector<float, 3> corner3_stq{corner1_stq[0], corner2_stq[1], corner2_stq[2]};
|
|
math::Vector<float, 3> corner4_stq{corner2_stq[0], corner1_stq[1], corner2_stq[2]};
|
|
|
|
if (m_prim_gl_state.gouraud_enable) {
|
|
// I'm not really sure what the GS does here.
|
|
ASSERT(false);
|
|
}
|
|
auto& corner3_rgba = corner2_rgba;
|
|
auto& corner4_rgba = corner2_rgba;
|
|
|
|
m_prim_buffer.push(corner1_rgba, corner1_vert, corner1_stq, scissor, 0, tcc, decal, fge,
|
|
use_uv);
|
|
m_prim_buffer.push(corner3_rgba, corner3_vert, corner3_stq, scissor, 0, tcc, decal, fge,
|
|
use_uv);
|
|
m_prim_buffer.push(corner2_rgba, corner2_vert, corner2_stq, scissor, 0, tcc, decal, fge,
|
|
use_uv);
|
|
m_prim_buffer.push(corner2_rgba, corner2_vert, corner2_stq, scissor, 0, tcc, decal, fge,
|
|
use_uv);
|
|
m_prim_buffer.push(corner4_rgba, corner4_vert, corner4_stq, scissor, 0, tcc, decal, fge,
|
|
use_uv);
|
|
m_prim_buffer.push(corner1_rgba, corner1_vert, corner1_stq, scissor, 0, tcc, decal, fge,
|
|
use_uv);
|
|
m_prim_building.building_idx = 0;
|
|
}
|
|
} break;
|
|
case GsPrim::Kind::TRI_STRIP: {
|
|
if (m_prim_building.building_idx == 3) {
|
|
m_prim_building.building_idx = 0;
|
|
}
|
|
|
|
if (m_prim_building.tri_strip_startup < 3) {
|
|
m_prim_building.tri_strip_startup++;
|
|
}
|
|
if (m_prim_building.tri_strip_startup >= 3) {
|
|
if (advance) {
|
|
for (int i = 0; i < 3; i++) {
|
|
m_prim_buffer.push(m_prim_building.building_rgba[i], m_prim_building.building_vert[i],
|
|
m_prim_building.building_stq[i], scissor, tex_unit, tcc, decal, fge,
|
|
use_uv);
|
|
}
|
|
}
|
|
}
|
|
|
|
} break;
|
|
|
|
case GsPrim::Kind::TRI:
|
|
if (m_prim_building.building_idx == 3) {
|
|
m_prim_building.building_idx = 0;
|
|
for (int i = 0; i < 3; i++) {
|
|
m_prim_buffer.push(m_prim_building.building_rgba[i], m_prim_building.building_vert[i],
|
|
m_prim_building.building_stq[i], scissor, tex_unit, tcc, decal, fge,
|
|
use_uv);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case GsPrim::Kind::TRI_FAN: {
|
|
if (m_prim_building.tri_strip_startup < 2) {
|
|
m_prim_building.tri_strip_startup++;
|
|
} else {
|
|
if (m_prim_building.building_idx == 2) {
|
|
// nothing.
|
|
} else if (m_prim_building.building_idx == 3) {
|
|
m_prim_building.building_idx = 1;
|
|
}
|
|
for (int i = 0; i < 3; i++) {
|
|
m_prim_buffer.push(m_prim_building.building_rgba[i], m_prim_building.building_vert[i],
|
|
m_prim_building.building_stq[i], scissor, tex_unit, tcc, decal, fge,
|
|
use_uv);
|
|
}
|
|
}
|
|
} break;
|
|
|
|
case GsPrim::Kind::LINE: {
|
|
if (m_prim_building.building_idx == 2) {
|
|
math::Vector<double, 3> pt0 = m_prim_building.building_vert[0].xyz().cast<double>();
|
|
math::Vector<double, 3> pt1 = m_prim_building.building_vert[1].xyz().cast<double>();
|
|
auto normal = (pt1 - pt0).normalized().cross(math::Vector<double, 3>{0, 0, 1});
|
|
|
|
double line_width = (1 << 19);
|
|
// debug_print_vtx(m_prim_building.building_vert[0]);
|
|
// debug_print_vtx(m_prim_building.building_vert[1]);
|
|
|
|
math::Vector<double, 3> a = pt0 + normal * line_width;
|
|
math::Vector<double, 3> b = pt1 + normal * line_width;
|
|
math::Vector<double, 3> c = pt0 - normal * line_width;
|
|
math::Vector<double, 3> d = pt1 - normal * line_width;
|
|
math::Vector<u32, 4> ai{a.x(), a.y(), a.z(), 0};
|
|
math::Vector<u32, 4> bi{b.x(), b.y(), b.z(), 0};
|
|
math::Vector<u32, 4> ci{c.x(), c.y(), c.z(), 0};
|
|
math::Vector<u32, 4> di{d.x(), d.y(), d.z(), 0};
|
|
|
|
// ACB:
|
|
m_prim_buffer.push(m_prim_building.building_rgba[0], ai, {}, scissor, 0, false, false,
|
|
false, false);
|
|
m_prim_buffer.push(m_prim_building.building_rgba[0], ci, {}, scissor, 0, false, false,
|
|
false, false);
|
|
m_prim_buffer.push(m_prim_building.building_rgba[1], bi, {}, scissor, 0, false, false,
|
|
false, false);
|
|
// b c d
|
|
m_prim_buffer.push(m_prim_building.building_rgba[1], bi, {}, scissor, 0, false, false,
|
|
false, false);
|
|
m_prim_buffer.push(m_prim_building.building_rgba[0], ci, {}, scissor, 0, false, false,
|
|
false, false);
|
|
m_prim_buffer.push(m_prim_building.building_rgba[1], di, {}, scissor, 0, false, false,
|
|
false, false);
|
|
//
|
|
|
|
m_prim_building.building_idx = 0;
|
|
}
|
|
} break;
|
|
|
|
case GsPrim::Kind::LINE_STRIP: {
|
|
if (m_prim_building.building_idx == 2) {
|
|
m_prim_building.building_idx = 0;
|
|
}
|
|
|
|
if (m_prim_building.tri_strip_startup < 2) {
|
|
m_prim_building.tri_strip_startup++;
|
|
}
|
|
if (m_prim_building.tri_strip_startup >= 2) {
|
|
if (advance) {
|
|
math::Vector<double, 3> pt0 = m_prim_building.building_vert[0].xyz().cast<double>();
|
|
math::Vector<double, 3> pt1 = m_prim_building.building_vert[1].xyz().cast<double>();
|
|
auto normal = (pt1 - pt0).normalized().cross(math::Vector<double, 3>{0, 0, 1});
|
|
|
|
double line_width = (1 << 19);
|
|
// debug_print_vtx(m_prim_building.building_vert[0]);
|
|
// debug_print_vtx(m_prim_building.building_vert[1]);
|
|
|
|
math::Vector<double, 3> a = pt0 + normal * line_width;
|
|
math::Vector<double, 3> b = pt1 + normal * line_width;
|
|
math::Vector<double, 3> c = pt0 - normal * line_width;
|
|
math::Vector<double, 3> d = pt1 - normal * line_width;
|
|
math::Vector<u32, 4> ai{a.x(), a.y(), a.z(), 0};
|
|
math::Vector<u32, 4> bi{b.x(), b.y(), b.z(), 0};
|
|
math::Vector<u32, 4> ci{c.x(), c.y(), c.z(), 0};
|
|
math::Vector<u32, 4> di{d.x(), d.y(), d.z(), 0};
|
|
|
|
// ACB:
|
|
m_prim_buffer.push(m_prim_building.building_rgba[0], ai, {}, scissor, 0, false, false,
|
|
false, false);
|
|
m_prim_buffer.push(m_prim_building.building_rgba[0], ci, {}, scissor, 0, false, false,
|
|
false, false);
|
|
m_prim_buffer.push(m_prim_building.building_rgba[1], bi, {}, scissor, 0, false, false,
|
|
false, false);
|
|
// b c d
|
|
m_prim_buffer.push(m_prim_building.building_rgba[1], bi, {}, scissor, 0, false, false,
|
|
false, false);
|
|
m_prim_buffer.push(m_prim_building.building_rgba[0], ci, {}, scissor, 0, false, false,
|
|
false, false);
|
|
m_prim_buffer.push(m_prim_building.building_rgba[1], di, {}, scissor, 0, false, false,
|
|
false, false);
|
|
//
|
|
}
|
|
}
|
|
} break;
|
|
|
|
default:
|
|
ASSERT_MSG(false, fmt::format("prim type {} is unsupported in {}.", (int)m_prim_building.kind,
|
|
name_and_id()));
|
|
}
|
|
}
|
|
|
|
void DirectRenderer::handle_xyzf2(u64 val,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
u32 x = val & 0xffff;
|
|
u32 y = (val >> 16) & 0xffff;
|
|
u32 z = (val >> 32) & 0xffffff;
|
|
u32 f = (val >> 56) & 0xff;
|
|
|
|
handle_xyzf2_common(x << 16, y << 16, z, f, render_state, prof, true);
|
|
}
|
|
|
|
void DirectRenderer::TestState::from_register(GsTest reg) {
|
|
current_register = reg;
|
|
alpha_test_enable = reg.alpha_test_enable();
|
|
if (alpha_test_enable) {
|
|
alpha_test = reg.alpha_test();
|
|
aref = reg.aref();
|
|
afail = reg.afail();
|
|
}
|
|
|
|
date = reg.date();
|
|
if (date) {
|
|
datm = reg.datm();
|
|
}
|
|
|
|
zte = reg.zte();
|
|
ztst = reg.ztest();
|
|
}
|
|
|
|
void DirectRenderer::BlendState::from_register(GsAlpha reg) {
|
|
current_register = reg;
|
|
a = reg.a_mode();
|
|
b = reg.b_mode();
|
|
c = reg.c_mode();
|
|
d = reg.d_mode();
|
|
fix = reg.fix();
|
|
}
|
|
|
|
void DirectRenderer::PrimGlState::from_register(GsPrim reg) {
|
|
current_register = reg;
|
|
gouraud_enable = reg.gouraud();
|
|
texture_enable = reg.tme();
|
|
fogging_enable = reg.fge();
|
|
aa_enable = reg.aa1();
|
|
use_uv = reg.fst();
|
|
ctxt = reg.ctxt();
|
|
fix = reg.fix();
|
|
}
|
|
|
|
DirectRenderer::PrimitiveBuffer::PrimitiveBuffer(int max_triangles) {
|
|
vertices.resize(max_triangles * 3);
|
|
max_verts = max_triangles * 3;
|
|
}
|
|
|
|
void DirectRenderer::PrimitiveBuffer::push(const math::Vector<u8, 4>& rgba,
|
|
const math::Vector<u32, 4>& vert,
|
|
const math::Vector<float, 3>& stq,
|
|
const math::Vector<float, 4>& scissor,
|
|
int unit,
|
|
bool tcc,
|
|
bool decal,
|
|
bool fog_enable,
|
|
bool use_uv) {
|
|
auto& v = vertices[vert_count];
|
|
v.rgba = rgba;
|
|
v.xyzf[0] = (float)vert[0] / (float)UINT32_MAX;
|
|
v.xyzf[0] += x_off;
|
|
v.xyzf[1] = (float)vert[1] / (float)UINT32_MAX;
|
|
v.xyzf[1] += y_off;
|
|
v.xyzf[2] = (float)vert[2] / (float)0xffffff;
|
|
v.xyzf[3] = (float)vert[3];
|
|
v.stq = stq;
|
|
v.tex_unit = unit;
|
|
v.tcc = tcc;
|
|
v.decal = decal;
|
|
v.fog_enable = fog_enable;
|
|
v.use_uv = use_uv;
|
|
v.scissor = scissor;
|
|
vert_count++;
|
|
}
|