mirror of
https://github.com/open-goal/jak-project
synced 2026-07-05 13:43:52 -04:00
85c3d76878
* [merc2] guard against bones in nans * zero out bone buffer, just to be safe
630 lines
23 KiB
C++
630 lines
23 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, BucketId 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);
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Handle the merc renderer switching to a different model.
|
|
*/
|
|
void Merc2::init_pc_model(const DmaTransfer& setup, SharedRenderState* render_state) {
|
|
// determine the name. We've packed this in a separate PC-port specific packet.
|
|
char name[128];
|
|
strcpy(name, (const char*)setup.data);
|
|
|
|
// get the model from the loader
|
|
m_current_model = render_state->loader->get_merc_model(name);
|
|
|
|
// update stats
|
|
m_stats.num_models++;
|
|
if (m_current_model) {
|
|
for (const auto& effect : m_current_model->model->effects) {
|
|
m_stats.num_effects++;
|
|
m_stats.num_predicted_draws += effect.draws.size();
|
|
for (const auto& draw : effect.draws) {
|
|
m_stats.num_predicted_tris += draw.num_triangles;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* Once-per-frame initialization
|
|
*/
|
|
void Merc2::init_for_frame(SharedRenderState* render_state) {
|
|
// reset state
|
|
m_current_model = std::nullopt;
|
|
m_stats = {};
|
|
|
|
// activate the merc shader used for all draws
|
|
render_state->shaders[ShaderId::MERC2].activate();
|
|
|
|
// set uniforms that we know from render_state
|
|
glUniform4f(m_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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void Merc2::init_shaders(ShaderLibrary& shaders) {
|
|
shaders[ShaderId::MERC2].activate();
|
|
m_uniforms.light_direction[0] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_dir0");
|
|
m_uniforms.light_direction[1] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_dir1");
|
|
m_uniforms.light_direction[2] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_dir2");
|
|
m_uniforms.light_color[0] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_col0");
|
|
m_uniforms.light_color[1] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_col1");
|
|
m_uniforms.light_color[2] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_col2");
|
|
m_uniforms.light_ambient = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "light_ambient");
|
|
|
|
m_uniforms.hvdf_offset = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "hvdf_offset");
|
|
m_uniforms.perspective[0] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "perspective0");
|
|
m_uniforms.perspective[1] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "perspective1");
|
|
m_uniforms.perspective[2] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "perspective2");
|
|
m_uniforms.perspective[3] = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "perspective3");
|
|
|
|
m_uniforms.fog = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "fog_constants");
|
|
m_uniforms.decal = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "decal_enable");
|
|
|
|
m_uniforms.fog_color = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "fog_color");
|
|
m_uniforms.perspective_matrix =
|
|
glGetUniformLocation(shaders[ShaderId::MERC2].id(), "perspective_matrix");
|
|
m_uniforms.ignore_alpha = glGetUniformLocation(shaders[ShaderId::MERC2].id(), "ignore_alpha");
|
|
}
|
|
|
|
/*!
|
|
* Main merc2 rendering.
|
|
*/
|
|
void Merc2::render(DmaFollower& dma, SharedRenderState* render_state, ScopedProfilerNode& prof) {
|
|
// skip if disabled
|
|
if (!m_enabled) {
|
|
while (dma.current_tag_offset() != render_state->next_bucket) {
|
|
dma.read_and_advance();
|
|
}
|
|
return;
|
|
}
|
|
|
|
init_for_frame(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;
|
|
}
|
|
|
|
/*!
|
|
* Store light values
|
|
*/
|
|
void Merc2::set_lights(const DmaTransfer& dma) {
|
|
memcpy(&m_current_lights, dma.data, sizeof(VuLights));
|
|
}
|
|
|
|
void Merc2::handle_matrix_dma(const DmaTransfer& dma) {
|
|
int slot = dma.vif0() & 0xff;
|
|
ASSERT(slot < MAX_SKEL_BONES);
|
|
memcpy(&m_skel_matrix_buffer[slot], dma.data, sizeof(MercMat));
|
|
}
|
|
|
|
/*!
|
|
* 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);
|
|
ASSERT(data0.vif0() == 0);
|
|
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;
|
|
}
|
|
ASSERT(data0.size_bytes == 0);
|
|
ASSERT(data0.vif0() == 0);
|
|
ASSERT(data0.vif1() == 0);
|
|
|
|
// if we reach here, there's stuff to draw
|
|
// this handles merc-specific setup DMA
|
|
handle_setup_dma(dma);
|
|
|
|
// 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) {
|
|
auto first = dma.read_and_advance();
|
|
|
|
// 10 quadword setup packet
|
|
ASSERT(first.size_bytes == 10 * 16);
|
|
// m_stats.str += fmt::format("Setup 0: {} {} {}", first.size_bytes / 16,
|
|
// first.vifcode0().print(), first.vifcode1().print());
|
|
|
|
// 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));
|
|
set_uniform(m_uniforms.hvdf_offset, m_low_memory.hvdf_offset);
|
|
set_uniform(m_uniforms.fog, m_low_memory.fog);
|
|
for (int i = 0; i < 4; i++) {
|
|
set_uniform(m_uniforms.perspective[i], m_low_memory.perspective[i]);
|
|
}
|
|
// todo rm.
|
|
glUniformMatrix4fv(m_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
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
bool tag_is_nothing_cnt(const DmaFollower& dma) {
|
|
return dma.current_tag().kind == DmaTag::Kind::CNT && 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();
|
|
|
|
if (init.vifcode1().kind == VifCode::Kind::PC_PORT) {
|
|
// we got a PC PORT packet. this contains some extra data to set up the model
|
|
flush_pending_model(render_state, prof);
|
|
init_pc_model(init, render_state);
|
|
ASSERT(tag_is_nothing_cnt(dma));
|
|
init = dma.read_and_advance(); // dummy tag in pc port
|
|
init = dma.read_and_advance();
|
|
}
|
|
|
|
// row stuff.
|
|
ASSERT(init.vifcode0().kind == VifCode::Kind::STROW);
|
|
ASSERT(init.size_bytes == 16);
|
|
// m_vif.row[0] = init.vif1();
|
|
// memcpy(m_vif.row + 1, init.data, 12);
|
|
u32 extra;
|
|
memcpy(&extra, init.data + 12, 4);
|
|
// ASSERT(extra == 0);
|
|
m_current_effect_enable_bits = extra;
|
|
m_current_ignore_alpha_bits = extra >> 16;
|
|
DmaTransfer next;
|
|
|
|
bool setting_up = true;
|
|
u32 mscal_addr = -1;
|
|
while (setting_up) {
|
|
next = dma.read_and_advance();
|
|
// fmt::print("next: {}", dma.current_tag().print());
|
|
u32 offset_in_data = 0;
|
|
// fmt::print("START {} : {} {}\n", next.size_bytes, next.vifcode0().print(),
|
|
// next.vifcode1().print());
|
|
auto vif0 = next.vifcode0();
|
|
switch (vif0.kind) {
|
|
case VifCode::Kind::NOP:
|
|
case VifCode::Kind::FLUSHE:
|
|
break;
|
|
case VifCode::Kind::STMOD:
|
|
ASSERT(vif0.immediate == 0 || vif0.immediate == 1);
|
|
// m_vif.stmod = vif0.immediate;
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
}
|
|
|
|
auto vif1 = next.vifcode1();
|
|
switch (vif1.kind) {
|
|
case VifCode::Kind::UNPACK_V4_8: {
|
|
VifCodeUnpack up(vif1);
|
|
offset_in_data += 4 * vif1.num;
|
|
} break;
|
|
case VifCode::Kind::UNPACK_V4_32: {
|
|
VifCodeUnpack up(vif1);
|
|
if (up.addr_qw == 132 && vif1.num == 8) {
|
|
set_lights(next);
|
|
} else if (vif1.num == 7) {
|
|
handle_matrix_dma(next);
|
|
}
|
|
offset_in_data += 16 * vif1.num;
|
|
} break;
|
|
case VifCode::Kind::MSCAL:
|
|
// fmt::print("cal\n");
|
|
mscal_addr = vif1.immediate;
|
|
ASSERT(next.size_bytes == 0);
|
|
setting_up = false;
|
|
break;
|
|
default:
|
|
ASSERT(false);
|
|
}
|
|
|
|
ASSERT(offset_in_data <= next.size_bytes);
|
|
if (offset_in_data < next.size_bytes) {
|
|
ASSERT((offset_in_data % 4) == 0);
|
|
u32 leftover = next.size_bytes - offset_in_data;
|
|
if (leftover < 16) {
|
|
for (u32 i = 0; i < leftover; i++) {
|
|
ASSERT(next.data[offset_in_data + i] == 0);
|
|
}
|
|
} else {
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*!
|
|
* 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, float scale) {
|
|
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;
|
|
|
|
// scale the transformation matrix (todo: can we move this to the extraction)
|
|
// and copy to the large bone buffer.
|
|
for (int j = 0; j < 3; j++) {
|
|
shader_mat[bv++] = skel_mat.tmat[j] * scale;
|
|
}
|
|
shader_mat[bv++] = skel_mat.tmat[3];
|
|
|
|
for (int j = 0; j < 3; j++) {
|
|
shader_mat[bv++] = skel_mat.nmat[j];
|
|
}
|
|
|
|
// we could include the effect of the perspective matrix here.
|
|
// for (int j = 0; j < 3; j++) {
|
|
// tbone_buffer[i][j] = vf15.elementwise_multiply(bone_mat[j]);
|
|
// tbone_buffer[i][j].w() += p.w() * bone_mat[j].z();
|
|
// tbone_buffer[i][j] *= scale;
|
|
// }
|
|
//
|
|
// tbone_buffer[i][3] = vf15.elementwise_multiply(bone_mat[3]) +
|
|
// m_low_memory.perspective[3]; tbone_buffer[i][3].w() += p.w() * bone_mat[3].z();
|
|
|
|
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;
|
|
}
|
|
|
|
u32 first_bone = alloc_bones(bone_count, model->scale_xyz);
|
|
|
|
// 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];
|
|
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;
|
|
}
|
|
}
|
|
|
|
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
|
|
3, // 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
|
|
3, // 3 floats per vert
|
|
GL_UNSIGNED_BYTE, // u8's
|
|
sizeof(tfrag3::MercVertex), //
|
|
(void*)offsetof(tfrag3::MercVertex, mats[0]) // offset in array
|
|
);
|
|
|
|
int last_tex = -1;
|
|
int last_light = -1;
|
|
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);
|
|
|
|
for (u32 di = 0; di < lev_bucket.next_free_draw; di++) {
|
|
auto& draw = lev_bucket.draws[di];
|
|
glUniform1i(m_uniforms.ignore_alpha, draw.ignore_alpha);
|
|
if ((int)draw.texture != last_tex) {
|
|
glBindTexture(GL_TEXTURE_2D, lev->textures.at(draw.texture));
|
|
last_tex = draw.texture;
|
|
}
|
|
|
|
if ((int)draw.light_idx != last_light) {
|
|
set_uniform(m_uniforms.light_direction[0], m_lights_buffer[draw.light_idx].direction0);
|
|
set_uniform(m_uniforms.light_direction[1], m_lights_buffer[draw.light_idx].direction1);
|
|
set_uniform(m_uniforms.light_direction[2], m_lights_buffer[draw.light_idx].direction2);
|
|
set_uniform(m_uniforms.light_color[0], m_lights_buffer[draw.light_idx].color0);
|
|
set_uniform(m_uniforms.light_color[1], m_lights_buffer[draw.light_idx].color1);
|
|
set_uniform(m_uniforms.light_color[2], m_lights_buffer[draw.light_idx].color2);
|
|
set_uniform(m_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(m_uniforms.decal, draw.mode.get_decal());
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
m_next_free_light = 0;
|
|
m_next_free_bone_vector = 0;
|
|
m_next_free_level_bucket = 0;
|
|
}
|