mirror of
https://github.com/open-goal/jak-project
synced 2026-09-07 03:30:01 -04:00
0fcc7eb8e9
This adds environment mapping support to `Merc2`, and turns it on for Jak 1 and Jak 2. - The performance is much better - Jak 1 can be toggled back to the old behavior with `(set! *emerc-hack* #f)`. The new environment mapping is identical to the old one everywhere I checked. - Jak 1 still falls back to generic for ripple/texscroll/blerc/eyes - there's still no dynamic texture or vertex updating support. The eye detection stuff will sometimes flag stuff as eyes which is not eyes, which is fine, but means that generic will be used in some places where emerc could be used. For example, the shiny plates on jak's arm will be drawn with generic because jak has eyes. - Jak 2 hasn't been checked super carefully against PCSX2 yet. - Jak 2 still isn't technically using emerc, but instead putting emerc models in the merc bucket. - The interface to merc is a lot different now and totally custom OpenGOAL DMA code. The original merc drawing asm doesn't run anymore. - The FR3 format changed - Something funky going on with foreground lighting in escape, but doesn't seem to be related to this change? Performance comparison, jak 1, in likely the most generic-merc heavy spot:  
693 lines
25 KiB
C++
693 lines
25 KiB
C++
#include "Merc2.h"
|
|
|
|
#include "game/graphics/opengl_renderer/background/background_common.h"
|
|
|
|
#include "third-party/imgui/imgui.h"
|
|
|
|
Merc2::Merc2(const std::string& name, int my_id) : BucketRenderer(name, my_id) {
|
|
glGenVertexArrays(1, &m_vao);
|
|
glBindVertexArray(m_vao);
|
|
|
|
glGenBuffers(1, &m_bones_buffer);
|
|
glBindBuffer(GL_UNIFORM_BUFFER, m_bones_buffer);
|
|
std::vector<u8> temp(MAX_SHADER_BONE_VECTORS * sizeof(math::Vector4f));
|
|
glBufferData(GL_UNIFORM_BUFFER, MAX_SHADER_BONE_VECTORS * sizeof(math::Vector4f), temp.data(),
|
|
GL_DYNAMIC_DRAW);
|
|
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
|
|
|
GLint val;
|
|
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &val);
|
|
if (val <= 16) {
|
|
// somehow doubt this can happen, but just in case
|
|
m_opengl_buffer_alignment = 1;
|
|
} else {
|
|
m_opengl_buffer_alignment = val / 16; // number of bone vectors
|
|
if (m_opengl_buffer_alignment * 16 != (u32)val) {
|
|
ASSERT_MSG(false,
|
|
fmt::format("opengl uniform buffer alignment is {}, which is strange\n", val));
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < MAX_LEVELS; i++) {
|
|
auto& draws = m_level_draw_buckets.emplace_back();
|
|
draws.draws.resize(MAX_DRAWS_PER_LEVEL);
|
|
draws.envmap_draws.resize(MAX_ENVMAP_DRAWS_PER_LEVEL);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Handle the merc renderer switching to a different model.
|
|
*/
|
|
void Merc2::init_pc_model(const DmaTransfer& setup, SharedRenderState* render_state) {
|
|
// ;; name (128 char, 8 qw)
|
|
// ;; lights (7 qw x 1)
|
|
// ;; matrix slot string (128 char, 8 qw)
|
|
// ;; matrices (7 qw x N)
|
|
// ;; flags (num-effects, effect-alpha-ignore, effect-disable)
|
|
// ;; fades (u32 x N), padding to qw aligned
|
|
|
|
// Part 1: name
|
|
const u8* input_data = setup.data;
|
|
char name[128];
|
|
strcpy(name, (const char*)setup.data);
|
|
m_current_model = render_state->loader->get_merc_model(name);
|
|
input_data += 128;
|
|
|
|
// Part 2: lights
|
|
memcpy(&m_current_lights, input_data, sizeof(VuLights));
|
|
input_data += sizeof(VuLights);
|
|
|
|
// Part 3: matrix slot string
|
|
auto* matrix_array = (const u32*)(input_data + 128);
|
|
int i;
|
|
for (i = 0; i < 128; i++) {
|
|
if (input_data[i] == 0xff) {
|
|
break;
|
|
}
|
|
u32 addr;
|
|
memcpy(&addr, &matrix_array[i * 4], 4);
|
|
const u8* real_addr = setup.data - setup.data_offset + addr;
|
|
memcpy(&m_skel_matrix_buffer[input_data[i]], real_addr, sizeof(MercMat));
|
|
}
|
|
input_data += 128 + 16 * i;
|
|
|
|
// Part 4: flags
|
|
auto* flags = (const u32*)input_data;
|
|
int num_effects = flags[0];
|
|
m_current_ignore_alpha_bits = flags[1];
|
|
m_current_effect_enable_bits = flags[2];
|
|
input_data += 16;
|
|
|
|
// Part 5: fades
|
|
for (int ei = 0; ei < num_effects; ei++) {
|
|
for (int j = 0; j < 4; j++) {
|
|
m_fade_buffer[ei * 4 + j] = input_data[ei * 4 + j];
|
|
}
|
|
}
|
|
|
|
if (m_current_model) {
|
|
m_stats.num_models++;
|
|
for (const auto& effect : m_current_model->model->effects) {
|
|
bool envmap = effect.has_envmap;
|
|
m_stats.num_effects++;
|
|
m_stats.num_predicted_draws += effect.draws.size();
|
|
if (envmap) {
|
|
m_stats.num_envmap_effects++;
|
|
m_stats.num_predicted_draws += effect.draws.size();
|
|
}
|
|
for (const auto& draw : effect.draws) {
|
|
m_stats.num_predicted_tris += draw.num_triangles;
|
|
if (envmap) {
|
|
m_stats.num_predicted_tris += draw.num_triangles;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
m_stats.num_missing_models++;
|
|
}
|
|
}
|
|
|
|
void Merc2::draw_debug_window() {
|
|
ImGui::Text("Models : %d", m_stats.num_models);
|
|
ImGui::Text("Effects : %d", m_stats.num_effects);
|
|
ImGui::Text("Draws (p): %d", m_stats.num_predicted_draws);
|
|
ImGui::Text("Tris (p): %d", m_stats.num_predicted_tris);
|
|
ImGui::Text("Bones : %d", m_stats.num_bones_uploaded);
|
|
ImGui::Text("Lights : %d", m_stats.num_lights);
|
|
ImGui::Text("Dflush : %d", m_stats.num_draw_flush);
|
|
|
|
ImGui::Text("EEffects : %d", m_stats.num_envmap_effects);
|
|
ImGui::Text("ETris : %d", m_stats.num_envmap_tris);
|
|
}
|
|
|
|
void Merc2::init_shaders(ShaderLibrary& shaders) {
|
|
init_shader_common(shaders[ShaderId::MERC2], &m_merc_uniforms, true);
|
|
init_shader_common(shaders[ShaderId::EMERC], &m_emerc_uniforms, false);
|
|
m_emerc_uniforms.fade = glGetUniformLocation(shaders[ShaderId::EMERC].id(), "fade");
|
|
}
|
|
|
|
void Merc2::init_shader_common(Shader& shader, Uniforms* uniforms, bool include_lights) {
|
|
auto id = shader.id();
|
|
shader.activate();
|
|
if (include_lights) {
|
|
uniforms->light_direction[0] = glGetUniformLocation(id, "light_dir0");
|
|
uniforms->light_direction[1] = glGetUniformLocation(id, "light_dir1");
|
|
uniforms->light_direction[2] = glGetUniformLocation(id, "light_dir2");
|
|
uniforms->light_color[0] = glGetUniformLocation(id, "light_col0");
|
|
uniforms->light_color[1] = glGetUniformLocation(id, "light_col1");
|
|
uniforms->light_color[2] = glGetUniformLocation(id, "light_col2");
|
|
uniforms->light_ambient = glGetUniformLocation(id, "light_ambient");
|
|
}
|
|
|
|
uniforms->hvdf_offset = glGetUniformLocation(id, "hvdf_offset");
|
|
|
|
uniforms->fog = glGetUniformLocation(id, "fog_constants");
|
|
uniforms->decal = glGetUniformLocation(id, "decal_enable");
|
|
|
|
uniforms->fog_color = glGetUniformLocation(id, "fog_color");
|
|
uniforms->perspective_matrix = glGetUniformLocation(id, "perspective_matrix");
|
|
uniforms->ignore_alpha = glGetUniformLocation(id, "ignore_alpha");
|
|
|
|
uniforms->gfx_hack_no_tex = glGetUniformLocation(id, "gfx_hack_no_tex");
|
|
}
|
|
|
|
void Merc2::switch_to_merc2(SharedRenderState* render_state) {
|
|
render_state->shaders[ShaderId::MERC2].activate();
|
|
|
|
// set uniforms that we know from render_state
|
|
glUniform4f(m_merc_uniforms.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(m_merc_uniforms.gfx_hack_no_tex, Gfx::g_global_settings.hack_no_tex);
|
|
}
|
|
|
|
void Merc2::switch_to_emerc(SharedRenderState* render_state) {
|
|
render_state->shaders[ShaderId::EMERC].activate();
|
|
// set uniforms that we know from render_state
|
|
glUniform4f(m_emerc_uniforms.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(m_emerc_uniforms.gfx_hack_no_tex, Gfx::g_global_settings.hack_no_tex);
|
|
}
|
|
|
|
/*!
|
|
* Main merc2 rendering.
|
|
*/
|
|
void Merc2::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) {
|
|
m_stats = {};
|
|
|
|
// skip if disabled
|
|
if (!m_enabled) {
|
|
while (dma.current_tag_offset() != render_state->next_bucket) {
|
|
dma.read_and_advance();
|
|
}
|
|
return;
|
|
}
|
|
m_current_model = std::nullopt;
|
|
switch_to_merc2(render_state);
|
|
|
|
// iterate through the dma chain, filling buckets
|
|
handle_all_dma(dma, render_state, prof);
|
|
|
|
// flush model data to buckets
|
|
flush_pending_model(render_state, prof);
|
|
// flush buckets to draws
|
|
flush_draw_buckets(render_state, prof);
|
|
}
|
|
|
|
u32 Merc2::alloc_lights(const VuLights& lights) {
|
|
ASSERT(m_next_free_light < MAX_LIGHTS);
|
|
m_stats.num_lights++;
|
|
u32 light_idx = m_next_free_light;
|
|
m_lights_buffer[m_next_free_light++] = lights;
|
|
static_assert(sizeof(VuLights) == 7 * 16);
|
|
return light_idx;
|
|
}
|
|
|
|
std::string Merc2::ShaderMercMat::to_string() const {
|
|
return fmt::format("tmat:\n{}\n{}\n{}\n{}\n", tmat[0].to_string_aligned(),
|
|
tmat[1].to_string_aligned(), tmat[2].to_string_aligned(),
|
|
tmat[3].to_string_aligned());
|
|
}
|
|
|
|
/*!
|
|
* Main MERC2 function to handle DMA
|
|
*/
|
|
void Merc2::handle_all_dma(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
// process the first tag. this is just jumping to the merc-specific dma.
|
|
auto data0 = dma.read_and_advance();
|
|
ASSERT(data0.vif1() == 0 || data0.vifcode1().kind == VifCode::Kind::NOP);
|
|
ASSERT(data0.vif0() == 0 || data0.vifcode0().kind == VifCode::Kind::NOP ||
|
|
data0.vifcode0().kind == VifCode::Kind::MARK);
|
|
ASSERT(data0.size_bytes == 0);
|
|
if (dma.current_tag().kind == DmaTag::Kind::CALL) {
|
|
// renderer didn't run, let's just get out of here.
|
|
for (int i = 0; i < 4; i++) {
|
|
dma.read_and_advance();
|
|
}
|
|
ASSERT(dma.current_tag_offset() == render_state->next_bucket);
|
|
return;
|
|
}
|
|
|
|
if (dma.current_tag_offset() == render_state->next_bucket) {
|
|
return;
|
|
}
|
|
// if we reach here, there's stuff to draw
|
|
// this handles merc-specific setup DMA
|
|
handle_setup_dma(dma, render_state);
|
|
|
|
// handle each merc transfer
|
|
while (dma.current_tag_offset() != render_state->next_bucket) {
|
|
handle_merc_chain(dma, render_state, prof);
|
|
}
|
|
ASSERT(dma.current_tag_offset() == render_state->next_bucket);
|
|
}
|
|
|
|
namespace {
|
|
void set_uniform(GLuint uniform, const math::Vector3f& val) {
|
|
glUniform3f(uniform, val.x(), val.y(), val.z());
|
|
}
|
|
void set_uniform(GLuint uniform, const math::Vector4f& val) {
|
|
glUniform4f(uniform, val.x(), val.y(), val.z(), val.w());
|
|
}
|
|
} // namespace
|
|
|
|
void Merc2::handle_setup_dma(DmaFollower& dma, SharedRenderState* render_state) {
|
|
auto first = dma.read_and_advance();
|
|
|
|
// 10 quadword setup packet
|
|
ASSERT(first.size_bytes == 10 * 16);
|
|
|
|
// transferred vifcodes
|
|
{
|
|
auto vif0 = first.vifcode0();
|
|
auto vif1 = first.vifcode1();
|
|
// STCYCL 4, 4
|
|
ASSERT(vif0.kind == VifCode::Kind::STCYCL);
|
|
auto vif0_st = VifCodeStcycl(vif0);
|
|
ASSERT(vif0_st.cl == 4 && vif0_st.wl == 4);
|
|
// STMOD
|
|
ASSERT(vif1.kind == VifCode::Kind::STMOD);
|
|
ASSERT(vif1.immediate == 0);
|
|
}
|
|
|
|
// 1 qw with 4 vifcodes.
|
|
u32 vifcode_data[4];
|
|
memcpy(vifcode_data, first.data, 16);
|
|
{
|
|
auto vif0 = VifCode(vifcode_data[0]);
|
|
ASSERT(vif0.kind == VifCode::Kind::BASE);
|
|
ASSERT(vif0.immediate == MercDataMemory::BUFFER_BASE);
|
|
auto vif1 = VifCode(vifcode_data[1]);
|
|
ASSERT(vif1.kind == VifCode::Kind::OFFSET);
|
|
ASSERT((s16)vif1.immediate == MercDataMemory::BUFFER_OFFSET);
|
|
auto vif2 = VifCode(vifcode_data[2]);
|
|
ASSERT(vif2.kind == VifCode::Kind::NOP);
|
|
auto vif3 = VifCode(vifcode_data[3]);
|
|
ASSERT(vif3.kind == VifCode::Kind::UNPACK_V4_32);
|
|
VifCodeUnpack up(vif3);
|
|
ASSERT(up.addr_qw == MercDataMemory::LOW_MEMORY);
|
|
ASSERT(!up.use_tops_flag);
|
|
ASSERT(vif3.num == 8);
|
|
}
|
|
|
|
// 8 qw's of low memory data
|
|
memcpy(&m_low_memory, first.data + 16, sizeof(LowMemory));
|
|
|
|
switch_to_merc2(render_state);
|
|
set_uniform(m_merc_uniforms.hvdf_offset, m_low_memory.hvdf_offset);
|
|
set_uniform(m_merc_uniforms.fog, m_low_memory.fog);
|
|
glUniformMatrix4fv(m_merc_uniforms.perspective_matrix, 1, GL_FALSE,
|
|
&m_low_memory.perspective[0].x());
|
|
switch_to_emerc(render_state);
|
|
set_uniform(m_emerc_uniforms.hvdf_offset, m_low_memory.hvdf_offset);
|
|
set_uniform(m_emerc_uniforms.fog, m_low_memory.fog);
|
|
glUniformMatrix4fv(m_emerc_uniforms.perspective_matrix, 1, GL_FALSE,
|
|
&m_low_memory.perspective[0].x());
|
|
|
|
// 1 qw with another 4 vifcodes.
|
|
u32 vifcode_final_data[4];
|
|
memcpy(vifcode_final_data, first.data + 16 + sizeof(LowMemory), 16);
|
|
{
|
|
ASSERT(VifCode(vifcode_final_data[0]).kind == VifCode::Kind::FLUSHE);
|
|
ASSERT(vifcode_final_data[1] == 0);
|
|
ASSERT(vifcode_final_data[2] == 0);
|
|
VifCode mscal(vifcode_final_data[3]);
|
|
ASSERT(mscal.kind == VifCode::Kind::MSCAL);
|
|
ASSERT(mscal.immediate == 0);
|
|
}
|
|
|
|
// TODO: process low memory initialization
|
|
|
|
if (render_state->version == GameVersion::Jak1) {
|
|
auto second = dma.read_and_advance();
|
|
ASSERT(second.size_bytes == 32); // setting up test register.
|
|
auto nothing = dma.read_and_advance();
|
|
ASSERT(nothing.size_bytes == 0);
|
|
ASSERT(nothing.vif0() == 0);
|
|
ASSERT(nothing.vif1() == 0);
|
|
} else {
|
|
auto second = dma.read_and_advance();
|
|
ASSERT(second.size_bytes == 48); // setting up test/zbuf register.
|
|
// todo z write mask stuff.
|
|
auto nothing = dma.read_and_advance();
|
|
ASSERT(nothing.size_bytes == 0);
|
|
ASSERT(nothing.vif0() == 0);
|
|
ASSERT(nothing.vif1() == 0);
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
bool tag_is_nothing_next(const DmaFollower& dma) {
|
|
return dma.current_tag().kind == DmaTag::Kind::NEXT && dma.current_tag().qwc == 0 &&
|
|
dma.current_tag_vif0() == 0 && dma.current_tag_vif1() == 0;
|
|
}
|
|
} // namespace
|
|
|
|
void Merc2::handle_merc_chain(DmaFollower& dma,
|
|
SharedRenderState* render_state,
|
|
ScopedProfilerNode& prof) {
|
|
while (tag_is_nothing_next(dma)) {
|
|
auto nothing = dma.read_and_advance();
|
|
ASSERT(nothing.size_bytes == 0);
|
|
}
|
|
if (dma.current_tag().kind == DmaTag::Kind::CALL) {
|
|
for (int i = 0; i < 4; i++) {
|
|
dma.read_and_advance();
|
|
}
|
|
return;
|
|
}
|
|
|
|
auto init = dma.read_and_advance();
|
|
int skip_count = 2;
|
|
if (render_state->version == GameVersion::Jak2) {
|
|
skip_count = 1;
|
|
}
|
|
|
|
while (init.vifcode1().kind == VifCode::Kind::PC_PORT) {
|
|
flush_pending_model(render_state, prof);
|
|
init_pc_model(init, render_state);
|
|
for (int i = 0; i < skip_count; i++) {
|
|
auto link = dma.read_and_advance();
|
|
ASSERT(link.vifcode0().kind == VifCode::Kind::NOP);
|
|
ASSERT(link.vifcode1().kind == VifCode::Kind::NOP);
|
|
ASSERT(link.size_bytes == 0);
|
|
}
|
|
init = dma.read_and_advance();
|
|
}
|
|
|
|
if (init.vifcode0().kind == VifCode::Kind::FLUSHA) {
|
|
int num_skipped = 0;
|
|
while (dma.current_tag_offset() != render_state->next_bucket) {
|
|
dma.read_and_advance();
|
|
num_skipped++;
|
|
}
|
|
ASSERT(num_skipped < 4);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Queue up some bones to be included in the bone buffer.
|
|
* Returns the index of the first bone vector.
|
|
*/
|
|
u32 Merc2::alloc_bones(int count) {
|
|
u32 first_bone_vector = m_next_free_bone_vector;
|
|
ASSERT(count * 8 + first_bone_vector <= MAX_SHADER_BONE_VECTORS);
|
|
|
|
// model should have under 128 bones.
|
|
ASSERT(count <= MAX_SKEL_BONES);
|
|
|
|
// iterate over each bone we need
|
|
for (int i = 0; i < count; i++) {
|
|
auto& skel_mat = m_skel_matrix_buffer[i];
|
|
auto* shader_mat = &m_shader_bone_vector_buffer[m_next_free_bone_vector];
|
|
int bv = 0;
|
|
|
|
// and copy to the large bone buffer.
|
|
for (int j = 0; j < 4; j++) {
|
|
shader_mat[bv++] = skel_mat.tmat[j];
|
|
}
|
|
|
|
for (int j = 0; j < 3; j++) {
|
|
shader_mat[bv++] = skel_mat.nmat[j];
|
|
}
|
|
|
|
m_next_free_bone_vector += 8;
|
|
}
|
|
|
|
auto b0 = m_next_free_bone_vector;
|
|
m_next_free_bone_vector += m_opengl_buffer_alignment - 1;
|
|
m_next_free_bone_vector /= m_opengl_buffer_alignment;
|
|
m_next_free_bone_vector *= m_opengl_buffer_alignment;
|
|
ASSERT(b0 <= m_next_free_bone_vector);
|
|
ASSERT(first_bone_vector + count * 8 <= m_next_free_bone_vector);
|
|
return first_bone_vector;
|
|
}
|
|
/*!
|
|
* Flush a model to draw buckets
|
|
*/
|
|
void Merc2::flush_pending_model(SharedRenderState* render_state, ScopedProfilerNode& prof) {
|
|
if (!m_current_model) {
|
|
return;
|
|
}
|
|
|
|
const LevelData* lev = m_current_model->level;
|
|
const tfrag3::MercModel* model = m_current_model->model;
|
|
|
|
int bone_count = model->max_bones + 1;
|
|
|
|
if (m_next_free_light >= MAX_LIGHTS) {
|
|
fmt::print("MERC2 out of lights, consider increasing MAX_LIGHTS\n");
|
|
flush_draw_buckets(render_state, prof);
|
|
}
|
|
|
|
if (m_next_free_bone_vector + m_opengl_buffer_alignment + bone_count * 8 >
|
|
MAX_SHADER_BONE_VECTORS) {
|
|
fmt::print("MERC2 out of bones, consider increasing MAX_SHADER_BONE_VECTORS\n");
|
|
flush_draw_buckets(render_state, prof);
|
|
}
|
|
|
|
// find a level bucket
|
|
LevelDrawBucket* lev_bucket = nullptr;
|
|
for (u32 i = 0; i < m_next_free_level_bucket; i++) {
|
|
if (m_level_draw_buckets[i].level == lev) {
|
|
lev_bucket = &m_level_draw_buckets[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!lev_bucket) {
|
|
// no existing bucket
|
|
if (m_next_free_level_bucket >= m_level_draw_buckets.size()) {
|
|
// out of room, flush
|
|
// fmt::print("MERC2 out of levels, consider increasing MAX_LEVELS\n");
|
|
flush_draw_buckets(render_state, prof);
|
|
// and retry the whole thing.
|
|
flush_pending_model(render_state, prof);
|
|
return;
|
|
}
|
|
// alloc a new one
|
|
lev_bucket = &m_level_draw_buckets[m_next_free_level_bucket++];
|
|
lev_bucket->reset();
|
|
lev_bucket->level = lev;
|
|
}
|
|
|
|
if (lev_bucket->next_free_draw + model->max_draws >= lev_bucket->draws.size()) {
|
|
// out of room, flush
|
|
fmt::print("MERC2 out of draws, consider increasing MAX_DRAWS_PER_LEVEL\n");
|
|
flush_draw_buckets(render_state, prof);
|
|
// and retry the whole thing.
|
|
flush_pending_model(render_state, prof);
|
|
return;
|
|
}
|
|
|
|
if (lev_bucket->next_free_envmap_draw + model->max_draws >= lev_bucket->envmap_draws.size()) {
|
|
// out of room, flush
|
|
fmt::print("MERC2 out of envmap draws, consider increasing MAX_ENVMAP_DRAWS_PER_LEVEL\n");
|
|
// or, use a more accurate max_draws for envmap.
|
|
flush_draw_buckets(render_state, prof);
|
|
// and retry the whole thing.
|
|
flush_pending_model(render_state, prof);
|
|
return;
|
|
}
|
|
|
|
u32 first_bone = alloc_bones(bone_count);
|
|
|
|
// allocate lights
|
|
u32 lights = alloc_lights(m_current_lights);
|
|
//
|
|
for (size_t ei = 0; ei < model->effects.size(); ei++) {
|
|
if (!(m_current_effect_enable_bits & (1 << ei))) {
|
|
continue;
|
|
}
|
|
|
|
u8 ignore_alpha = (m_current_ignore_alpha_bits & (1 << ei));
|
|
auto& effect = model->effects[ei];
|
|
if (effect.has_envmap) {
|
|
bool nonzero_fade = false;
|
|
for (int i = 0; i < 4; i++) {
|
|
if (m_fade_buffer[4 * ei + i]) {
|
|
nonzero_fade = true;
|
|
break;
|
|
}
|
|
}
|
|
if (nonzero_fade) {
|
|
for (auto& mdraw : effect.draws) {
|
|
Draw* draw = &lev_bucket->envmap_draws[lev_bucket->next_free_envmap_draw++];
|
|
draw->first_index = mdraw.first_index;
|
|
draw->index_count = mdraw.index_count;
|
|
draw->mode = effect.envmap_mode;
|
|
draw->texture = effect.envmap_texture;
|
|
draw->first_bone = first_bone;
|
|
draw->light_idx = lights;
|
|
draw->num_triangles = mdraw.num_triangles;
|
|
draw->ignore_alpha = false;
|
|
for (int i = 0; i < 4; i++) {
|
|
draw->fade[i] = m_fade_buffer[4 * ei + i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (auto& mdraw : effect.draws) {
|
|
Draw* draw = &lev_bucket->draws[lev_bucket->next_free_draw++];
|
|
draw->first_index = mdraw.first_index;
|
|
draw->index_count = mdraw.index_count;
|
|
draw->mode = mdraw.mode;
|
|
draw->texture = mdraw.tree_tex_id;
|
|
draw->first_bone = first_bone;
|
|
draw->light_idx = lights;
|
|
draw->num_triangles = mdraw.num_triangles;
|
|
draw->ignore_alpha = ignore_alpha;
|
|
for (int i = 0; i < 4; i++) {
|
|
draw->fade[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_current_model = std::nullopt;
|
|
}
|
|
|
|
void Merc2::flush_draw_buckets(SharedRenderState* render_state, ScopedProfilerNode& prof) {
|
|
m_stats.num_draw_flush++;
|
|
|
|
for (u32 li = 0; li < m_next_free_level_bucket; li++) {
|
|
const auto& lev_bucket = m_level_draw_buckets[li];
|
|
const auto* lev = lev_bucket.level;
|
|
glBindVertexArray(m_vao);
|
|
glBindBuffer(GL_ARRAY_BUFFER, lev->merc_vertices);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, lev->merc_indices);
|
|
|
|
glEnable(GL_PRIMITIVE_RESTART);
|
|
glPrimitiveRestartIndex(UINT32_MAX);
|
|
glEnableVertexAttribArray(0);
|
|
glEnableVertexAttribArray(1);
|
|
glEnableVertexAttribArray(2);
|
|
glEnableVertexAttribArray(3);
|
|
glEnableVertexAttribArray(4);
|
|
glEnableVertexAttribArray(5);
|
|
glEnable(GL_DEPTH_TEST);
|
|
glDepthFunc(GL_GEQUAL);
|
|
|
|
glVertexAttribPointer(0, // location 0 in the shader
|
|
3, // 3 values per vert
|
|
GL_FLOAT, // floats
|
|
GL_FALSE, // normalized
|
|
sizeof(tfrag3::MercVertex), // stride
|
|
(void*)offsetof(tfrag3::MercVertex, pos) // offset (0)
|
|
);
|
|
|
|
glVertexAttribPointer(1, // location 1 in the
|
|
3, // 3 values per vert
|
|
GL_FLOAT, // floats
|
|
GL_FALSE, // normalized
|
|
sizeof(tfrag3::MercVertex), // stride
|
|
(void*)offsetof(tfrag3::MercVertex, normal[0]) // offset (0)
|
|
);
|
|
|
|
glVertexAttribPointer(2, // location 1 in the
|
|
3, // 3 values per vert
|
|
GL_FLOAT, // floats
|
|
GL_FALSE, // normalized
|
|
sizeof(tfrag3::MercVertex), // stride
|
|
(void*)offsetof(tfrag3::MercVertex, weights[0]) // offset (0)
|
|
);
|
|
|
|
glVertexAttribPointer(3, // location 1 in the shader
|
|
2, // 3 values per vert
|
|
GL_FLOAT, // floats
|
|
GL_FALSE, // normalized
|
|
sizeof(tfrag3::MercVertex), // stride
|
|
(void*)offsetof(tfrag3::MercVertex, st[0]) // offset (0)
|
|
);
|
|
|
|
glVertexAttribPointer(4, // location 1 in the shader
|
|
4, // 3 values per vert
|
|
GL_UNSIGNED_BYTE, // floats
|
|
GL_TRUE, // normalized
|
|
sizeof(tfrag3::MercVertex), // stride
|
|
(void*)offsetof(tfrag3::MercVertex, rgba[0]) // offset (0)
|
|
);
|
|
|
|
glVertexAttribIPointer(5, // location 0 in the
|
|
4, // 3 floats per vert
|
|
GL_UNSIGNED_BYTE, // u8's
|
|
sizeof(tfrag3::MercVertex), //
|
|
(void*)offsetof(tfrag3::MercVertex, mats[0]) // offset in array
|
|
);
|
|
|
|
m_stats.num_bones_uploaded += m_next_free_bone_vector;
|
|
|
|
glBindBuffer(GL_UNIFORM_BUFFER, m_bones_buffer);
|
|
glBufferSubData(GL_UNIFORM_BUFFER, 0, m_next_free_bone_vector * sizeof(math::Vector4f),
|
|
m_shader_bone_vector_buffer);
|
|
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
|
|
|
switch_to_merc2(render_state);
|
|
do_draws(lev_bucket.draws.data(), lev, lev_bucket.next_free_draw, m_merc_uniforms, prof, false,
|
|
render_state);
|
|
if (lev_bucket.next_free_envmap_draw) {
|
|
switch_to_emerc(render_state);
|
|
do_draws(lev_bucket.envmap_draws.data(), lev, lev_bucket.next_free_envmap_draw,
|
|
m_emerc_uniforms, prof, true, render_state);
|
|
}
|
|
}
|
|
|
|
m_next_free_light = 0;
|
|
m_next_free_bone_vector = 0;
|
|
m_next_free_level_bucket = 0;
|
|
}
|
|
|
|
void Merc2::do_draws(const Draw* draw_array,
|
|
const LevelData* lev,
|
|
u32 num_draws,
|
|
const Uniforms& uniforms,
|
|
ScopedProfilerNode& prof,
|
|
bool set_fade,
|
|
SharedRenderState* render_state) {
|
|
int last_tex = -1;
|
|
int last_light = -1;
|
|
for (u32 di = 0; di < num_draws; di++) {
|
|
auto& draw = draw_array[di];
|
|
glUniform1i(uniforms.ignore_alpha, draw.ignore_alpha);
|
|
if ((int)draw.texture != last_tex) {
|
|
if (draw.texture < lev->textures.size()) {
|
|
glBindTexture(GL_TEXTURE_2D, lev->textures.at(draw.texture));
|
|
} else {
|
|
fmt::print("Invalid draw.texture is {}, would have crashed.\n", draw.texture);
|
|
}
|
|
last_tex = draw.texture;
|
|
}
|
|
|
|
if ((int)draw.light_idx != last_light && !set_fade) {
|
|
set_uniform(uniforms.light_direction[0], m_lights_buffer[draw.light_idx].direction0);
|
|
set_uniform(uniforms.light_direction[1], m_lights_buffer[draw.light_idx].direction1);
|
|
set_uniform(uniforms.light_direction[2], m_lights_buffer[draw.light_idx].direction2);
|
|
set_uniform(uniforms.light_color[0], m_lights_buffer[draw.light_idx].color0);
|
|
set_uniform(uniforms.light_color[1], m_lights_buffer[draw.light_idx].color1);
|
|
set_uniform(uniforms.light_color[2], m_lights_buffer[draw.light_idx].color2);
|
|
set_uniform(uniforms.light_ambient, m_lights_buffer[draw.light_idx].ambient);
|
|
last_light = draw.light_idx;
|
|
}
|
|
setup_opengl_from_draw_mode(draw.mode, GL_TEXTURE0, true);
|
|
|
|
glUniform1i(uniforms.decal, draw.mode.get_decal());
|
|
|
|
if (set_fade) {
|
|
math::Vector4f fade =
|
|
math::Vector4f(draw.fade[0], draw.fade[1], draw.fade[2], draw.fade[3]) / 255.f;
|
|
set_uniform(uniforms.fade, fade);
|
|
ASSERT(draw.mode.get_alpha_blend() == DrawMode::AlphaBlend::SRC_0_DST_DST);
|
|
// glBindTexture(GL_TEXTURE_2D, render_state->texture_pool->get_placeholder_texture());
|
|
}
|
|
|
|
prof.add_draw_call();
|
|
prof.add_tri(draw.num_triangles);
|
|
glBindBufferRange(GL_UNIFORM_BUFFER, 1, m_bones_buffer,
|
|
sizeof(math::Vector4f) * draw.first_bone, 128 * sizeof(ShaderMercMat));
|
|
glDrawElements(GL_TRIANGLE_STRIP, draw.index_count, GL_UNSIGNED_INT,
|
|
(void*)(sizeof(u32) * draw.first_index));
|
|
}
|
|
} |