Files
jak-project/game/graphics/opengl_renderer/foreground/Merc2.cpp
T
water111 0fcc7eb8e9 [merc2] Support emerc (#2147)
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:

![image](https://user-images.githubusercontent.com/48171810/213882718-feb2ab59-95a9-44a2-b0e5-95fba860c7b0.png)

![image](https://user-images.githubusercontent.com/48171810/213882736-8dbbf4c9-6bbf-4d0b-96ce-78d63274660c.png)
2023-01-22 18:30:31 -05:00

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));
}
}