diff --git a/src/noggit/MapView.cpp b/src/noggit/MapView.cpp index cff44d42..90a3f00b 100644 --- a/src/noggit/MapView.cpp +++ b/src/noggit/MapView.cpp @@ -2815,7 +2815,12 @@ void MapView::saveMinimap(MinimapRenderSettings* settings) _main_window->statusBar()->addPermanentWidget(cancel_btn); - //connect(this, &MapView::updateProgress, progress, &QProgressBar::setValue); + connect(this, &MapView::updateProgress, + [=](int value) + { + + progress->setValue(value); + }); // setup combined image if necessary if (settings->combined_minimap) @@ -2920,7 +2925,15 @@ void MapView::saveMinimap(MinimapRenderSettings* settings) _main_window->statusBar()->addPermanentWidget(cancel_btn); - connect(this, &MapView::updateProgress, progress, &QProgressBar::setValue); + connect(this, &MapView::updateProgress, + [=](int value) + { + // This weirdness is required due to a bug on Linux when QT repaint crashes due to too many events + // being passed through. TODO: this potentially only masks the issue, which may reappear on faster + // hardware. + if (progress->value() != value) + progress->setValue(value); + }); // setup combined image if necessary if (settings->combined_minimap) diff --git a/src/noggit/Model.cpp b/src/noggit/Model.cpp index 3c54ccb6..3967ca5a 100644 --- a/src/noggit/Model.cpp +++ b/src/noggit/Model.cpp @@ -23,8 +23,8 @@ Model::Model(const std::string& filename, Noggit::NoggitRenderContext context) : AsyncObject(filename) - , _finished_upload(false) , _context(context) + , _renderer(this) { memset(&header, 0, sizeof(ModelHeader)); } @@ -53,11 +53,6 @@ void Model::finishLoading() blend_override = M2Array(f, ofs_blend_override, n_blend_override); } - - _vertex_box_points = math::box_points ( misc::transform_model_box_coords(header.bounding_box_min) - , misc::transform_model_box_coords(header.bounding_box_max) - ); - animated = isAnimated(f); // isAnimated will set animGeometry and animTextures trans = 1.0f; @@ -291,9 +286,9 @@ void Model::initCommon(const BlizzardArchive::ClientFile& f) return; } - ModelView const* view = reinterpret_cast(g.getBuffer()); - uint16_t const* indexLookup = reinterpret_cast(g.getBuffer() + view->ofs_index); - uint16_t const* triangles = reinterpret_cast(g.getBuffer() + view->ofs_triangle); + auto view = reinterpret_cast(g.getBuffer()); + auto indexLookup = reinterpret_cast(g.getBuffer() + view->ofs_index); + auto triangles = reinterpret_cast(g.getBuffer() + view->ofs_triangle); _indices.resize (view->n_triangle); @@ -302,8 +297,8 @@ void Model::initCommon(const BlizzardArchive::ClientFile& f) } // render ops - ModelGeoset const* model_geosets = reinterpret_cast(g.getBuffer() + view->ofs_submesh); - ModelTexUnit const* texture_unit = reinterpret_cast(g.getBuffer() + view->ofs_texture_unit); + auto model_geosets = reinterpret_cast(g.getBuffer() + view->ofs_submesh); + auto texture_unit = reinterpret_cast(g.getBuffer() + view->ofs_texture_unit); _texture_lookup = M2Array(f, header.ofsTexLookup, header.nTexLookup); _texture_animation_lookups = M2Array(f, header.ofsTexAnimLookup, header.nTexAnimLookup); @@ -317,595 +312,18 @@ void Model::initCommon(const BlizzardArchive::ClientFile& f) _render_flags = M2Array(f, header.ofsRenderFlags, header.nRenderFlags); - _render_passes.reserve(view->n_texture_unit); - for (size_t j = 0; jn_texture_unit; j++) - { - size_t geoset = texture_unit[j].submesh; - - ModelRenderPass pass(texture_unit[j], this); - pass.ordering_thingy = model_geosets[geoset].BoundingBox[0].x; - - pass.index_start = model_geosets[geoset].istart; - pass.index_count = model_geosets[geoset].icount; - pass.vertex_start = model_geosets[geoset].vstart; - pass.vertex_end = pass.vertex_start + model_geosets[geoset].vcount; - - _render_passes.push_back(std::move(pass)); - } + _renderer.initRenderPasses(view, texture_unit, model_geosets); g.close(); - fix_shader_id_blend_override(); - fix_shader_id_layer(); - compute_pixel_shader_ids(); - - for (auto& pass : _render_passes) - { - pass.init_uv_types(this); - } - - // transparent parts come later - std::sort(_render_passes.begin(), _render_passes.end()); - // add fake geometry for selection - if (_render_passes.empty()) + if (_renderer.renderPasses().empty()) { _fake_geometry.emplace(this); } } } -void Model::fix_shader_id_blend_override() -{ - for (auto& pass : _render_passes) - { - if (pass.shader_id & 0x8000) - { - continue; - } - - int shader = 0; - bool blend_mode_override = (header.Flags & 8); - - // fuckporting check - if (pass.texture_coord_combo_index + pass.texture_count - 1 >= _texture_unit_lookup.size()) - { - LogDebug << "wrong texture coord combo index on fuckported model: " << _file_key.stringRepr() << std::endl; - // use default stuff - pass.shader_id = 0; - pass.texture_count = 1; - - continue; - } - - if (!blend_mode_override) - { - uint16_t texture_unit_lookup = _texture_unit_lookup[pass.texture_coord_combo_index]; - - if (_render_flags[pass.renderflag_index].blend) - { - shader = 1; - - if (texture_unit_lookup == 0xFFFF) - { - shader |= 0x8; - } - } - - shader <<= 4; - - if (texture_unit_lookup == 1) - { - shader |= 0x4000; - } - } - else - { - uint16_t runtime_shader_val[2] = { 0, 0 }; - - for (int i = 0; i < pass.texture_count; ++i) - { - uint16_t override_blend = blend_override[pass.shader_id + i]; - uint16_t texture_unit_lookup = _texture_unit_lookup[pass.texture_coord_combo_index + i]; - - if (i == 0 && _render_flags[pass.renderflag_index].blend == 0) - { - override_blend = 0; - } - - runtime_shader_val[i] = override_blend; - - if (texture_unit_lookup == 0xFFFF) - { - runtime_shader_val[i] |= 0x8; - } - - if (texture_unit_lookup == 1 && i + 1 == pass.texture_count) - { - shader |= 0x4000; - } - } - - shader |= (runtime_shader_val[1] & 0xFFFF) | ((runtime_shader_val[0] << 4) & 0xFFFF); - } - - pass.shader_id = shader; - } -} - -void Model::fix_shader_id_layer() -{ - int non_layered_count = 0; - - for (auto const& pass : _render_passes) - { - if (pass.material_layer <= 0) - { - non_layered_count++; - } - } - - if (non_layered_count < _render_passes.size()) - { - std::vector passes; - - ModelRenderPass* first_pass = nullptr; - bool need_reducing = false; - uint16_t previous_render_flag = -1, some_flags = 0; - - for (auto& pass : _render_passes) - { - if (pass.renderflag_index == previous_render_flag) - { - need_reducing = true; - continue; - } - - previous_render_flag = pass.renderflag_index; - - uint8_t lower_bits = pass.shader_id & 0x7; - - if (pass.material_layer == 0) - { - if (pass.texture_count >= 1 && _render_flags[pass.renderflag_index].blend == 0) - { - pass.shader_id &= 0xFF8F; - } - - first_pass = &pass; - } - - bool xor_unlit = ((_render_flags[pass.renderflag_index].flags.unlit ^ _render_flags[first_pass->renderflag_index].flags.unlit) & 1) == 0; - - if ((some_flags & 0xFF) == 1) - { - if ((_render_flags[pass.renderflag_index].blend == 1 || _render_flags[pass.renderflag_index].blend == 2) - && pass.texture_count == 1 - && xor_unlit - && pass.texture_combo_index == first_pass->texture_combo_index - ) - { - if (_transparency_lookup[pass.transparency_combo_index] == _transparency_lookup[first_pass->transparency_combo_index]) - { - pass.shader_id = 0x8000; - first_pass->shader_id = 0x8001; - - some_flags = (some_flags & 0xFF00) | 3; - - // current pass removed (not needed) - continue; - } - } - - some_flags = (some_flags & 0xFF00); - } - - int16_t texture_unit_lookup = _texture_unit_lookup[pass.texture_coord_combo_index]; - - if ((some_flags & 0xFF) < 2) - { - if ((_render_flags[pass.renderflag_index].blend == 0) && (pass.texture_count == 2) && ((lower_bits == 4) || (lower_bits == 6))) - { - if (texture_unit_lookup == 0 && (_texture_unit_lookup[pass.texture_coord_combo_index + 1] == -1)) - { - some_flags = (some_flags & 0xFF00) | 1; - } - } - } - - if ((some_flags >> 8) != 0) - { - if ((some_flags >> 8) == 1) - { - if (((_render_flags[pass.renderflag_index].blend != 4) && (_render_flags[pass.renderflag_index].blend != 6)) || (pass.texture_count != 1) || (texture_unit_lookup >= 0)) - { - some_flags &= 0xFF00; - } - else if (_transparency_lookup[pass.transparency_combo_index] == _transparency_lookup[first_pass->transparency_combo_index]) - { - pass.shader_id = 0x8000; - first_pass->shader_id = _render_flags[pass.renderflag_index].blend != 4 ? 0xE : 0x8002; - - some_flags = (some_flags & 0xFF) | (2 << 8); - - first_pass->texture_count = 2; - - first_pass->textures[1] = pass.texture_combo_index; - first_pass->uv_animations[1] = pass.animation_combo_index; - - // current pass removed (merged with the previous one) - continue; - } - } - else - { - if ((some_flags >> 8) != 2) - { - continue; - } - - if ( ((_render_flags[pass.renderflag_index].blend != 2) && (_render_flags[pass.renderflag_index].blend != 1)) - || (pass.texture_count != 1) - || xor_unlit - || ((pass.texture_combo_index & 0xff) != (first_pass->texture_combo_index & 0xff)) - ) - { - some_flags &= 0xFF00; - } - else if (_transparency_lookup[pass.transparency_combo_index] == _transparency_lookup[first_pass->transparency_combo_index]) - { - // current pass ignored/removed - pass.shader_id = 0x8000; - first_pass->shader_id = ((first_pass->shader_id == 0x8002 ? 2 : 0) - 0x7FFF) & 0xFFFF; - some_flags = (some_flags & 0xFF) | (3 << 8); - continue; - } - } - some_flags = (some_flags & 0xFF); - } - - if ((_render_flags[pass.renderflag_index].blend == 0) && (pass.texture_count == 1) && (texture_unit_lookup == 0)) - { - some_flags = (some_flags & 0xFF) | (1 << 8); - } - - // setup texture and anim lookup indices - pass.textures[0] = pass.texture_combo_index; - pass.textures[1] = pass.texture_count > 1 ? pass.texture_combo_index + 1 : 0; - pass.uv_animations[0] = pass.animation_combo_index; - pass.uv_animations[1] = pass.texture_count > 1 ? pass.animation_combo_index + 1 : 0; - - passes.push_back(pass); - } - - if (need_reducing) - { - previous_render_flag = -1; - for (int i = 0; i < passes.size(); ++i) - { - auto& pass = _render_passes[i]; - uint16_t renderflag_index = pass.renderflag_index; - - if (renderflag_index == previous_render_flag) - { - pass.shader_id = _render_passes[i - 1].shader_id; - pass.texture_count = _render_passes[i - 1].texture_count; - pass.texture_combo_index = _render_passes[i - 1].texture_combo_index; - pass.texture_coord_combo_index = _render_passes[i - 1].texture_coord_combo_index; - } - else - { - previous_render_flag = renderflag_index; - } - } - } - - _render_passes = passes; - } - // no layering, just setting some infos - else - { - for (auto& pass : _render_passes) - { - pass.textures[0] = pass.texture_combo_index; - pass.textures[1] = pass.texture_count > 1 ? pass.texture_combo_index + 1 : 0; - pass.uv_animations[0] = pass.animation_combo_index; - pass.uv_animations[1] = pass.texture_count > 1 ? pass.animation_combo_index + 1 : 0; - } - } -} - - -ModelRenderPass::ModelRenderPass(ModelTexUnit const& tex_unit, Model* m) - : ModelTexUnit(tex_unit) - , blend_mode(m->_render_flags[renderflag_index].blend) -{ -} - -bool ModelRenderPass::prepare_draw(OpenGL::Scoped::use_program& m2_shader, Model *m, OpenGL::M2RenderState& model_render_state) -{ - if (!m->showGeosets[submesh] || !pixel_shader) - { - return false; - } - - // COLOUR - // Get the colour and transparency and check that we should even render - glm::vec4 mesh_color = glm::vec4(1.0f, 1.0f, 1.0f, m->trans); // ?? - glm::vec4 emissive_color = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); - - auto const& renderflag(m->_render_flags[renderflag_index]); - - // emissive colors - if (color_index != -1 && m->_colors[color_index].color.uses(0)) - { - ::glm::vec3 c (m->_colors[color_index].color.getValue (0, m->_anim_time, m->_global_animtime)); - if (m->_colors[color_index].opacity.uses (m->_current_anim_seq)) - { - mesh_color.w = m->_colors[color_index].opacity.getValue (m->_current_anim_seq, m->_anim_time, m->_global_animtime); - } - - mesh_color.x = c.x; mesh_color.y = c.y; mesh_color.z = c.z; - - emissive_color = glm::vec4(c.x,c.y,c.z, mesh_color.w); - } - - // opacity - if (transparency_combo_index != 0xFFFF && transparency_combo_index < m->_transparency_lookup.size()) - { - auto& transparency (m->_transparency[m->_transparency_lookup[transparency_combo_index]].trans); - if (transparency.uses (0)) - { - mesh_color.w = mesh_color.w * transparency.getValue(0, m->_anim_time, m->_global_animtime); - } - } - - // exit and return false before affecting the opengl render state - if (!((mesh_color.w > 0) && (color_index == -1 || emissive_color.w > 0))) - { - return false; - } - - - if (model_render_state.blend != renderflag.blend) - { - switch (static_cast(renderflag.blend)) - { - default: - case M2Blend::Opaque: - case M2Blend::Alpha_Key: - gl.disable(GL_BLEND); - break; - case M2Blend::Alpha: - gl.enable(GL_BLEND); - gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - break; - case M2Blend::No_Add_Alpha: - gl.enable(GL_BLEND); - gl.blendFunc(GL_ONE, GL_ONE); - break; - case M2Blend::Add: - gl.enable(GL_BLEND); - gl.blendFunc(GL_SRC_ALPHA, GL_ONE); - break; - case M2Blend::Mod: - gl.enable(GL_BLEND); - gl.blendFunc(GL_DST_COLOR, GL_ZERO); - break; - case M2Blend::Mod2x: - gl.enable(GL_BLEND); - gl.blendFunc(GL_DST_COLOR, GL_SRC_COLOR); - break; - } - - m2_shader.uniform("blend_mode", static_cast(renderflag.blend)); - model_render_state.blend = renderflag.blend; - } - - if (model_render_state.backface_cull != !renderflag.flags.two_sided) - { - if (renderflag.flags.two_sided) - { - gl.disable(GL_CULL_FACE); - } - else - { - gl.enable(GL_CULL_FACE); - } - - model_render_state.backface_cull = !renderflag.flags.two_sided; - } - - if (model_render_state.z_buffered != renderflag.flags.z_buffered) - { - if (renderflag.flags.z_buffered) - { - gl.depthMask(GL_FALSE); - } - else - { - gl.depthMask(GL_TRUE); - } - - model_render_state.z_buffered = renderflag.flags.z_buffered; - } - - if (model_render_state.unfogged != renderflag.flags.unfogged) - { - m2_shader.uniform("unfogged", (int)renderflag.flags.unfogged); - model_render_state.unfogged = renderflag.flags.unfogged; - } - - if (model_render_state.unlit != renderflag.flags.unlit) - { - m2_shader.uniform("unlit", (int)renderflag.flags.unlit); - model_render_state.unlit = renderflag.flags.unlit; - } - - if (texture_count > 1) - { - bind_texture(1, m, model_render_state, m2_shader); - } - - bind_texture(0, m, model_render_state, m2_shader); - - GLint tu1 = static_cast(tu_lookups[0]), tu2 = static_cast(tu_lookups[1]); - - if (model_render_state.tex_unit_lookups[0] != tu1) - { - m2_shader.uniform("tex_unit_lookup_1", tu1); - model_render_state.tex_unit_lookups[0] = tu1; - } - - if (model_render_state.tex_unit_lookups[1] != tu2) - { - m2_shader.uniform("tex_unit_lookup_2", tu2); - model_render_state.tex_unit_lookups[1] = tu2; - } - - int16_t tex_anim_lookup = m->_texture_animation_lookups[uv_animations[0]]; - static const glm::mat4x4 unit(glm::mat4x4(1)); - - if (tex_anim_lookup != -1) - { - m2_shader.uniform("tex_matrix_1", m->_texture_animations[tex_anim_lookup].mat); - if (texture_count > 1) - { - tex_anim_lookup = m->_texture_animation_lookups[uv_animations[1]]; - if (tex_anim_lookup != -1) - { - m2_shader.uniform("tex_matrix_2", m->_texture_animations[tex_anim_lookup].mat); - } - else - { - m2_shader.uniform("tex_matrix_2", unit); - } - } - } - else - { - m2_shader.uniform("tex_matrix_1", unit); - m2_shader.uniform("tex_matrix_2", unit); - } - - - GLint ps = static_cast(pixel_shader.value()); - if (model_render_state.pixel_shader != ps) - { - m2_shader.uniform("pixel_shader", ps); - model_render_state.pixel_shader = ps; - } - - m2_shader.uniform("mesh_color", mesh_color); - - return true; -} - -void ModelRenderPass::after_draw() -{ - gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); -} - -void ModelRenderPass::bind_texture(size_t index, Model* m, OpenGL::M2RenderState& model_render_state, OpenGL::Scoped::use_program& m2_shader) -{ - - uint16_t tex = m->_texture_lookup[textures[index]]; - - if (m->_specialTextures[tex] == -1) - { - auto& texture = m->_textures[tex]; - texture->upload(); - GLuint tex_array = texture->texture_array(); - int tex_index = texture->array_index(); - - gl.activeTexture(GL_TEXTURE0 + index + 1); - gl.bindTexture(GL_TEXTURE_2D_ARRAY, tex_array); - /* - if (model_render_state.tex_arrays[index] != tex_array) - { - gl.activeTexture(GL_TEXTURE0 + index + 1); - gl.bindTexture(GL_TEXTURE_2D_ARRAY, tex_array); - model_render_state.tex_arrays[index] = tex_array; - } - - - if (model_render_state.tex_indices[index] != tex_index) - { - m2_shader.uniform(index ? "tex2_index" : "tex1_index", tex_index); - model_render_state.tex_indices[index] = tex_index; - } - - */ - - m2_shader.uniform(index ? "tex2_index" : "tex1_index", tex_index); - model_render_state.tex_indices[index] = tex_index; - - } - else - { - auto& texture = m->_replaceTextures.at (m->_specialTextures[tex]); - texture->upload(); - GLuint tex_array = texture->texture_array(); - int tex_index = texture->array_index(); - - gl.activeTexture(GL_TEXTURE0 + index + 1); - gl.bindTexture(GL_TEXTURE_2D_ARRAY, tex_array); - - /* - if (model_render_state.tex_arrays[index] != tex_array) - { - gl.activeTexture(GL_TEXTURE0 + index + 1); - gl.bindTexture(GL_TEXTURE_2D_ARRAY, tex_array); - model_render_state.tex_arrays[index] = tex_array; - } - - if (model_render_state.tex_indices[index] != tex_index) - { - m2_shader.uniform(index ? "tex2_index" : "tex1_index", tex_index); - model_render_state.tex_indices[index] = tex_index; - } - - */ - - m2_shader.uniform(index ? "tex2_index" : "tex1_index", tex_index); - model_render_state.tex_indices[index] = tex_index; - - } -} - -void ModelRenderPass::init_uv_types(Model* m) -{ - tu_lookups[0] = texture_unit_lookup::none; - tu_lookups[1] = texture_unit_lookup::none; - - if (m->_texture_unit_lookup.size() < texture_coord_combo_index + texture_count) - { - LogError << "model: texture_coord_combo_index out of range " << m->file_key().stringRepr() << std::endl; - - for (int i = 0; i < texture_count; ++i) - { - switch (i) - { - case 0: tu_lookups[i] = texture_unit_lookup::t1; break; - case 1: tu_lookups[i] = texture_unit_lookup::t2; break; - } - } - - return; - - //throw std::out_of_range("model: texture_coord_combo_index out of range " + m->filename); - } - - for (int i = 0; i < texture_count; ++i) - { - switch (m->_texture_unit_lookup[texture_coord_combo_index + i]) - { - case (int16_t)(-1): tu_lookups[i] = texture_unit_lookup::environment; break; - case 0: tu_lookups[i] = texture_unit_lookup::t1; break; - case 1: tu_lookups[i] = texture_unit_lookup::t2; break; - } - } -} FakeGeometry::FakeGeometry(Model* m) { @@ -932,154 +350,6 @@ FakeGeometry::FakeGeometry(Model* m) }; } -namespace -{ - -// https://wowdev.wiki/M2/.skin/WotLK_shader_selection -std::optional GetPixelShader(uint16_t texture_count, uint16_t shader_id) -{ - uint16_t texture1_fragment_mode = (shader_id >> 4) & 7; - uint16_t texture2_fragment_mode = shader_id & 7; - // uint16_t texture1_env_map = (shader_id >> 4) & 8; - // uint16_t texture2_env_map = shader_id & 8; - - std::optional pixel_shader; - - if (texture_count == 1) - { - switch (texture1_fragment_mode) - { - case 0: - pixel_shader = ModelPixelShader::Combiners_Opaque; - break; - case 2: - pixel_shader = ModelPixelShader::Combiners_Decal; - break; - case 3: - pixel_shader = ModelPixelShader::Combiners_Add; - break; - case 4: - pixel_shader = ModelPixelShader::Combiners_Mod2x; - break; - case 5: - pixel_shader = ModelPixelShader::Combiners_Fade; - break; - default: - pixel_shader = ModelPixelShader::Combiners_Mod; - break; - } - } - else - { - if (!texture1_fragment_mode) - { - switch (texture2_fragment_mode) - { - case 0: - pixel_shader = ModelPixelShader::Combiners_Opaque_Opaque; - break; - case 3: - pixel_shader = ModelPixelShader::Combiners_Opaque_Add; - break; - case 4: - pixel_shader = ModelPixelShader::Combiners_Opaque_Mod2x; - break; - case 6: - pixel_shader = ModelPixelShader::Combiners_Opaque_Mod2xNA; - break; - case 7: - pixel_shader = ModelPixelShader::Combiners_Opaque_AddNA; - break; - default: - pixel_shader = ModelPixelShader::Combiners_Opaque_Mod; - break; - } - } - else if (texture1_fragment_mode == 1) - { - switch (texture2_fragment_mode) - { - case 0: - pixel_shader = ModelPixelShader::Combiners_Mod_Opaque; - break; - case 3: - pixel_shader = ModelPixelShader::Combiners_Mod_Add; - break; - case 4: - pixel_shader = ModelPixelShader::Combiners_Mod_Mod2x; - break; - case 6: - pixel_shader = ModelPixelShader::Combiners_Mod_Mod2xNA; - break; - case 7: - pixel_shader = ModelPixelShader::Combiners_Mod_AddNA; - break; - default: - pixel_shader = ModelPixelShader::Combiners_Mod_Mod; - break; - } - } - else if (texture1_fragment_mode == 3) - { - if (texture2_fragment_mode == 1) - { - pixel_shader = ModelPixelShader::Combiners_Add_Mod; - } - } - else if (texture1_fragment_mode == 4 && texture2_fragment_mode == 4) - { - pixel_shader = ModelPixelShader::Combiners_Mod2x_Mod2x; - } - else if (texture2_fragment_mode == 1) - { - pixel_shader = ModelPixelShader::Combiners_Mod_Mod2x; - } - } - - - return pixel_shader; -} - -std::optional M2GetPixelShaderID (uint16_t texture_count, uint16_t shader_id) -{ - std::optional pixel_shader; - - if (!(shader_id & 0x8000)) - { - pixel_shader = GetPixelShader(texture_count, shader_id); - - if (!pixel_shader) - { - pixel_shader = GetPixelShader(texture_count, 0x11); - } - } - else - { - switch (shader_id & 0x7FFF) - { - case 1: - pixel_shader = ModelPixelShader::Combiners_Opaque_Mod2xNA_Alpha; - break; - case 2: - pixel_shader = ModelPixelShader::Combiners_Opaque_AddAlpha; - break; - case 3: - pixel_shader = ModelPixelShader::Combiners_Opaque_AddAlpha_Alpha; - break; - } - } - - return pixel_shader; -} -} - -void Model::compute_pixel_shader_ids() -{ - for (auto& pass : _render_passes) - { - pass.pixel_shader = M2GetPixelShaderID(pass.texture_count, pass.shader_id); - } -} void Model::initAnimated(const BlizzardArchive::ClientFile& f) { @@ -1233,10 +503,7 @@ void Model::animate(glm::mat4x4 const& model_view, int anim_id, int anim_time) bone_counter++; } - { - OpenGL::Scoped::buffer_binder const binder (_bone_matrices_buffer); - gl.bufferSubData(GL_TEXTURE_BUFFER, 0, bone_matrices.size() * sizeof(glm::mat4x4), bone_matrices.data()); - } + _renderer.updateBoneMatrices(); // transform vertices @@ -1487,205 +754,6 @@ void Bone::calcMatrix(glm::mat4x4 const& model_view calc = true; } -void Model::draw( glm::mat4x4 const& model_view - , ModelInstance& instance - , OpenGL::Scoped::use_program& m2_shader - , OpenGL::M2RenderState& model_render_state - , math::frustum const& frustum - , const float& cull_distance - , const glm::vec3& camera - , int animtime - , display_mode display - , bool no_cull - ) -{ - if (!finishedLoading() || loading_failed()) - { - return; - } - - if (!no_cull && !instance.isInFrustum(frustum) && !instance.isInRenderDist(cull_distance, camera, display)) - { - return; - } - - if (!_finished_upload) - { - upload(); - } - - if (animated && (!animcalc || _per_instance_animation)) - { - animate(model_view, 0, animtime); - animcalc = true; - } - - OpenGL::Scoped::vao_binder const _(_vao); - - m2_shader.uniform("transform", instance.transformMatrix()); - - { - OpenGL::Scoped::buffer_binder const binder(_vertices_buffer); - m2_shader.attrib("pos", 3, GL_FLOAT, GL_FALSE, sizeof(ModelVertex), 0); - m2_shader.attrib("bones_weight", 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (ModelVertex), reinterpret_cast (sizeof (::glm::vec3))); - m2_shader.attrib("bones_indices", 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (ModelVertex), reinterpret_cast (sizeof (::glm::vec3) + 4)); - m2_shader.attrib("normal", 3, GL_FLOAT, GL_FALSE, sizeof(ModelVertex), reinterpret_cast (sizeof(::glm::vec3) + 8)); - m2_shader.attrib("texcoord1", 2, GL_FLOAT, GL_FALSE, sizeof(ModelVertex), reinterpret_cast (sizeof(::glm::vec3) * 2 + 8)); - m2_shader.attrib("texcoord2", 2, GL_FLOAT, GL_FALSE, sizeof(ModelVertex), reinterpret_cast (sizeof(::glm::vec3) * 2 + 8 + sizeof(glm::vec2))); - } - - OpenGL::Scoped::buffer_binder indices_binder(_indices_buffer); - - for (ModelRenderPass& p : _render_passes) - { - if (p.prepare_draw(m2_shader, this, model_render_state)) - { - gl.drawElements(GL_TRIANGLES, p.index_count, GL_UNSIGNED_SHORT, reinterpret_cast(p.index_start * sizeof(GLushort))); - p.after_draw(); - } - } - - gl.disable(GL_BLEND); - gl.enable(GL_CULL_FACE); - gl.depthMask(GL_TRUE); -} - -void Model::draw (glm::mat4x4 const& model_view - , std::vector const& instances - , OpenGL::Scoped::use_program& m2_shader - , OpenGL::M2RenderState& model_render_state - , math::frustum const& frustum - , const float& cull_distance - , const glm::vec3& camera - , int animtime - , bool all_boxes - , std::unordered_map& model_boxes_to_draw - , display_mode display - , bool no_cull - ) -{ - ZoneScopedN(NOGGIT_CURRENT_FUNCTION); - - { - ZoneScopedN("Model::draw() : uploads") - - if (!finishedLoading() || loading_failed()) - { - return; - } - - if (!_finished_upload) - { - upload(); - } - - if (!_vao_setup) - { - setupVAO(m2_shader); - } - } - - if (instances.empty()) - { - return; - } - - { - ZoneScopedN("Model::draw() : drawing") - - if (animated && (!animcalc || _per_instance_animation)) - { - animate(model_view, 0, animtime); - animcalc = true; - } - - // store the model count to draw the bounding boxes later - if (all_boxes || _hidden) - { - model_boxes_to_draw.emplace(this, instances.size()); - } - - /* - if (draw_particles && (!_particles.empty() || !_ribbons.empty())) - { - models_with_particles.emplace(this, n_visible_instances); - } - */ - - OpenGL::Scoped::vao_binder const _ (_vao); - - { - OpenGL::Scoped::buffer_binder const transform_binder (_transform_buffer); - gl.bufferData(GL_ARRAY_BUFFER, instances.size() * sizeof(::glm::mat4x4), instances.data(), GL_DYNAMIC_DRAW); - //m2_shader.attrib("transform", 0, 1); - } - - if (animBones) - { - gl.activeTexture(GL_TEXTURE0); - gl.bindTexture(GL_TEXTURE_BUFFER, _bone_matrices_buf_tex); - m2_shader.uniform("anim_bones", true); - } - else - { - m2_shader.uniform("anim_bones", false); - } - - OpenGL::Scoped::buffer_binder indices_binder(_indices_buffer); - - for (ModelRenderPass& p : _render_passes) - { - if (p.prepare_draw(m2_shader, this, model_render_state)) - { - gl.drawElementsInstanced(GL_TRIANGLES, p.index_count, GL_UNSIGNED_SHORT, reinterpret_cast(p.index_start * sizeof(GLushort)), instances.size()); - //p.after_draw(); - } - } - } - -} - -void Model::draw_particles( glm::mat4x4 const& model_view - , OpenGL::Scoped::use_program& particles_shader - , std::size_t instance_count - ) -{ - for (auto& p : _particles) - { - p.draw(model_view, particles_shader, _transform_buffer, instance_count); - } -} - -void Model::draw_ribbons( OpenGL::Scoped::use_program& ribbons_shader - , std::size_t instance_count - ) -{ - for (auto& r : _ribbons) - { - r.draw(ribbons_shader, _transform_buffer, instance_count); - } -} - -void Model::draw_box (OpenGL::Scoped::use_program& m2_box_shader, std::size_t box_count) -{ - - OpenGL::Scoped::vao_binder const _ (_box_vao); - - { - OpenGL::Scoped::buffer_binder const transform_binder (_transform_buffer); - m2_box_shader.attrib("transform", 0, 1); - } - - { - OpenGL::Scoped::buffer_binder const binder (_box_vbo); - m2_box_shader.attrib("position", 3, GL_FLOAT, GL_FALSE, 0, 0); - } - - OpenGL::Scoped::buffer_binder indices_binder(_box_indices_buffer); - - gl.drawElementsInstanced (GL_LINE_STRIP, _box_indices.size(), GL_UNSIGNED_SHORT, nullptr, box_count); -} - std::vector Model::intersect (glm::mat4x4 const& model_view, math::ray const& ray, int animtime) { @@ -1722,7 +790,7 @@ std::vector Model::intersect (glm::mat4x4 const& model_view, math::ray co return results; } - for (auto&& pass : _render_passes) + for (auto const& pass : _renderer.renderPasses()) { for (size_t i (pass.index_start); i < pass.index_start + pass.index_count; i += 3) { @@ -1751,69 +819,6 @@ void Model::lightsOff(OpenGL::light lbase) for (unsigned int i = 0, l = lbase; i const binder(_bone_matrices_buffer); - gl.bufferData(GL_TEXTURE_BUFFER, bone_matrices.size() * sizeof(glm::mat4x4), nullptr, GL_STREAM_DRAW); - gl.texBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, _bone_matrices_buffer); - } - - { - OpenGL::Scoped::buffer_binder const binder(_vertices_buffer); - gl.bufferData(GL_ARRAY_BUFFER, _vertices.size() * sizeof(ModelVertex), _vertices.data(), GL_STATIC_DRAW); - } - - { - OpenGL::Scoped::buffer_binder const binder (_box_vbo); - gl.bufferData (GL_ARRAY_BUFFER, _vertex_box_points.size() * sizeof (glm::vec3), _vertex_box_points.data(), GL_STATIC_DRAW); - } - - OpenGL::Scoped::buffer_binder indices_binder(_indices_buffer); - gl.bufferData (GL_ELEMENT_ARRAY_BUFFER, _indices.size() * sizeof(uint16_t), _indices.data(), GL_STATIC_DRAW); - - OpenGL::Scoped::buffer_binder box_indices_binder(_box_indices_buffer); - gl.bufferData (GL_ELEMENT_ARRAY_BUFFER, _box_indices.size() * sizeof(uint16_t), _box_indices.data(), GL_STATIC_DRAW); - - - _textureFilenames.clear(); - - _finished_upload = true; -} - -void Model::unload() -{ - _textures.clear(); - _buffers.unload(); - _vertex_arrays.unload(); - - if (_bone_matrices_buf_tex) - gl.deleteTextures(1, &_bone_matrices_buf_tex); - - for (auto& particle : _particles) - { - particle.unload(); - } - - for (auto& ribbon : _ribbons) - { - ribbon.unload(); - } - - _finished_upload = false; - _vao_setup = false; -} void Model::updateEmitters(float dt) { @@ -1828,25 +833,3 @@ void Model::updateEmitters(float dt) } } -void Model::setupVAO(OpenGL::Scoped::use_program& m2_shader) -{ - OpenGL::Scoped::vao_binder const _(_vao); - - { - OpenGL::Scoped::buffer_binder const binder (_vertices_buffer); - m2_shader.attrib("pos", 3, GL_FLOAT, GL_FALSE, sizeof (ModelVertex), 0); - m2_shader.attribi("bones_weight", 4, GL_UNSIGNED_BYTE, sizeof (ModelVertex), reinterpret_cast (sizeof (::glm::vec3))); - m2_shader.attribi("bones_indices",4, GL_UNSIGNED_BYTE, sizeof (ModelVertex), reinterpret_cast (sizeof (::glm::vec3) + 4)); - m2_shader.attrib("normal", 3, GL_FLOAT, GL_FALSE, sizeof (ModelVertex), reinterpret_cast (sizeof (::glm::vec3) + 8)); - m2_shader.attrib("texcoord1", 2, GL_FLOAT, GL_FALSE, sizeof (ModelVertex), reinterpret_cast (sizeof (::glm::vec3) * 2 + 8)); - m2_shader.attrib("texcoord2", 2, GL_FLOAT, GL_FALSE, sizeof (ModelVertex), reinterpret_cast (sizeof (::glm::vec3) * 2 + 8 + sizeof(glm::vec2))); - } - - { - OpenGL::Scoped::buffer_binder const transform_binder (_transform_buffer); - gl.bufferData(GL_ARRAY_BUFFER, 10 * sizeof(::glm::mat4x4), nullptr, GL_DYNAMIC_DRAW); - m2_shader.attrib("transform", 0, 1); - } - - _vao_setup = true; -} diff --git a/src/noggit/Model.h b/src/noggit/Model.h index ffa00358..184e8add 100644 --- a/src/noggit/Model.h +++ b/src/noggit/Model.h @@ -17,6 +17,7 @@ #include #include #include +#include class Bone; class Model; @@ -24,6 +25,12 @@ class ModelInstance; class ParticleSystem; class RibbonEmitter; +namespace Noggit::Rendering +{ + class ModelRender; + struct ModelRenderPass; +} + glm::vec3 fixCoordSystem(glm::vec3 v); @@ -97,89 +104,6 @@ struct ModelTransparency { }; -enum class M2Blend : uint16_t -{ - Opaque, - Alpha_Key, - Alpha, - No_Add_Alpha, - Add, - Mod, - Mod2x -}; - -enum class ModelPixelShader : uint16_t -{ - Combiners_Opaque, - Combiners_Decal, - Combiners_Add, - Combiners_Mod2x, - Combiners_Fade, - Combiners_Mod, - Combiners_Opaque_Opaque, - Combiners_Opaque_Add, - Combiners_Opaque_Mod2x, - Combiners_Opaque_Mod2xNA, - Combiners_Opaque_AddNA, - Combiners_Opaque_Mod, - Combiners_Mod_Opaque, - Combiners_Mod_Add, - Combiners_Mod_Mod2x, - Combiners_Mod_Mod2xNA, - Combiners_Mod_AddNA, - Combiners_Mod_Mod, - Combiners_Add_Mod, - Combiners_Mod2x_Mod2x, - Combiners_Opaque_Mod2xNA_Alpha, - Combiners_Opaque_AddAlpha, - Combiners_Opaque_AddAlpha_Alpha, -}; - -enum class texture_unit_lookup : int -{ - environment, - t1, - t2, - none -}; - - -struct ModelRenderPass : ModelTexUnit -{ - ModelRenderPass() = delete; - ModelRenderPass(ModelTexUnit const& tex_unit, Model* m); - - float ordering_thingy = 0.f; - uint16_t index_start = 0, index_count = 0, vertex_start = 0, vertex_end = 0; - uint16_t blend_mode = 0; - texture_unit_lookup tu_lookups[2]; - uint16_t textures[2]; - uint16_t uv_animations[2]; - std::optional pixel_shader; - - - bool prepare_draw(OpenGL::Scoped::use_program& m2_shader, Model *m, OpenGL::M2RenderState& model_render_state); - void after_draw(); - void bind_texture(size_t index, Model* m, OpenGL::M2RenderState& model_render_state, OpenGL::Scoped::use_program& m2_shader); - void init_uv_types(Model* m); - - bool operator< (const ModelRenderPass &m) const - { - if (priority_plane < m.priority_plane) - { - return true; - } - else if (priority_plane > m.priority_plane) - { - return false; - } - else - { - return blend_mode == m.blend_mode ? (ordering_thingy < m.ordering_thingy) : blend_mode < m.blend_mode; - } - } -}; - struct FakeGeometry { FakeGeometry(Model* m); @@ -202,6 +126,9 @@ struct ModelLight { class Model : public AsyncObject { + friend class Noggit::Rendering::ModelRender; + friend struct Noggit::Rendering::ModelRenderPass; + public: template static std::vector M2Array(BlizzardArchive::ClientFile const& f, uint32_t offset, uint32_t count) @@ -212,40 +139,6 @@ public: Model(const std::string& name, Noggit::NoggitRenderContext context ); - void draw(glm::mat4x4 const& model_view - , ModelInstance& instance - , OpenGL::Scoped::use_program& m2_shader - , OpenGL::M2RenderState& model_render_state - , math::frustum const& frustum - , const float& cull_distance - , const glm::vec3& camera - , int animtime - , display_mode display - , bool no_cull = false - ); - void draw (glm::mat4x4 const& model_view - , std::vector const& instances - , OpenGL::Scoped::use_program& m2_shader - , OpenGL::M2RenderState& model_render_state - , math::frustum const& frustum - , const float& cull_distance - , const glm::vec3& camera - , int animtime - , bool all_boxes - , std::unordered_map& model_boxes_to_draw - , display_mode display - , bool no_cull = false - ); - void draw_particles( glm::mat4x4 const& model_view - , OpenGL::Scoped::use_program& particles_shader - , std::size_t instance_count - ); - void draw_ribbons( OpenGL::Scoped::use_program& ribbons_shader - , std::size_t instance_count - ); - - void draw_box (OpenGL::Scoped::use_program& m2_box_shader, std::size_t box_count); - std::vector intersect (glm::mat4x4 const& model_view, math::ray const&, int animtime); void updateEmitters(float dt); @@ -268,6 +161,9 @@ public: return true; } + [[nodiscard]] + Noggit::Rendering::ModelRender* renderer() { return &_renderer; } + // =============================== // Toggles // =============================== @@ -295,8 +191,6 @@ public: float trans; bool animcalc; - void unload(); - private: bool _per_instance_animation; int _current_anim_seq; @@ -309,42 +203,12 @@ private: bool isAnimated(const BlizzardArchive::ClientFile& f); void initAnimated(const BlizzardArchive::ClientFile& f); - void fix_shader_id_blend_override(); - void fix_shader_id_layer(); - void compute_pixel_shader_ids(); - void animate(glm::mat4x4 const& model_view, int anim_id, int anim_time); void calcBones(glm::mat4x4 const& model_view, int anim, int time, int animation_time); void lightsOn(OpenGL::light lbase); void lightsOff(OpenGL::light lbase); - void upload(); - void setupVAO(OpenGL::Scoped::use_program& m2_shader); - - bool _finished_upload = false; - bool _vao_setup = false; - - std::vector _vertex_box_points; - - // buffers - OpenGL::Scoped::deferred_upload_buffers<6> _buffers; - OpenGL::Scoped::deferred_upload_vertex_arrays<2> _vertex_arrays; - - std::vector const _box_indices = {5, 7, 3, 2, 0, 1, 3, 1, 5, 4, 0, 4, 6, 2, 6, 7}; - - GLuint const& _vao = _vertex_arrays[0]; - GLuint const& _transform_buffer = _buffers[0]; - GLuint const& _vertices_buffer = _buffers[1]; - GLuint const& _indices_buffer = _buffers[3]; - GLuint const& _box_indices_buffer = _buffers[4]; - GLuint const& _bone_matrices_buffer = _buffers[5]; - - GLuint const& _box_vao = _vertex_arrays[1]; - GLuint const& _box_vbo = _buffers[2]; - - GLuint _bone_matrices_buf_tex; - // =============================== // Geometry // =============================== @@ -354,7 +218,6 @@ private: std::vector _indices; - std::vector _render_passes; std::optional _fake_geometry; // =============================== @@ -384,8 +247,8 @@ private: std::vector _transparency_lookup; std::vector _lights; - bool _hidden = false; + Noggit::Rendering::ModelRender _renderer; - friend struct ModelRenderPass; + bool _hidden = false; }; diff --git a/src/noggit/ModelHeaders.h b/src/noggit/ModelHeaders.h index d5a5e120..b9507e25 100644 --- a/src/noggit/ModelHeaders.h +++ b/src/noggit/ModelHeaders.h @@ -1,7 +1,7 @@ // This file is part of Noggit3, licensed under GNU General Public License (version 3). #pragma once #include -#include +#include #pragma pack(push,1) diff --git a/src/noggit/ModelManager.cpp b/src/noggit/ModelManager.cpp index c6bd4215..4f488add 100644 --- a/src/noggit/ModelManager.cpp +++ b/src/noggit/ModelManager.cpp @@ -72,7 +72,7 @@ void ModelManager::unload_all(Noggit::NoggitRenderContext context) _.context_aware_apply( [&] (BlizzardArchive::Listfile::FileKey const&, Model& model) { - model.unload(); + model.renderer()->unload(); } , context ); diff --git a/src/noggit/Sky.cpp b/src/noggit/Sky.cpp index 6dec5533..61f3a917 100644 --- a/src/noggit/Sky.cpp +++ b/src/noggit/Sky.cpp @@ -518,7 +518,7 @@ bool Skies::draw(glm::mat4x4 const& model_view m2_shader.uniform("tex_unit_lookup_2", 0); m2_shader.uniform("pixel_shader", 0); - model.model->draw(model_view, model, m2_shader, model_render_state, frustum, 1000000, camera_pos, animtime, display_mode::in_3D); + model.model->renderer()->draw(model_view, model, m2_shader, model_render_state, frustum, 1000000, camera_pos, animtime, display_mode::in_3D); } } // if it's night, draw the stars @@ -543,7 +543,7 @@ bool Skies::draw(glm::mat4x4 const& model_view m2_shader.uniform("tex_unit_lookup_2", 0); m2_shader.uniform("pixel_shader", 0); - stars.model->draw(model_view, stars, m2_shader, model_render_state, frustum, 1000000, camera_pos, animtime, display_mode::in_3D); + stars.model->renderer()->draw(model_view, stars, m2_shader, model_render_state, frustum, 1000000, camera_pos, animtime, display_mode::in_3D); } return true; diff --git a/src/noggit/rendering/ModelRender.cpp b/src/noggit/rendering/ModelRender.cpp new file mode 100644 index 00000000..623f6a49 --- /dev/null +++ b/src/noggit/rendering/ModelRender.cpp @@ -0,0 +1,1051 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). + +#include "ModelRender.hpp" +#include +#include +#include +#include +#include + + +using namespace Noggit::Rendering; + +ModelRender::ModelRender(Model* model) +: _model(model) +{ + +} + +void ModelRender::upload() +{ + _vertex_box_points = math::box_points( + misc::transform_model_box_coords(_model->header.bounding_box_min) + , misc::transform_model_box_coords(_model->header.bounding_box_max)); + + for (std::string const& texture : _model->_textureFilenames) + _model->_textures.emplace_back(texture, _model->_context); + + _buffers.upload(); + _vertex_arrays.upload(); + _bone_matrices_buf_tex = 0; + + if (_model->animBones) + { + gl.genTextures(1, &_bone_matrices_buf_tex); + + gl.bindTexture(GL_TEXTURE_BUFFER, _bone_matrices_buf_tex); + OpenGL::Scoped::buffer_binder const binder(_bone_matrices_buffer); + gl.bufferData(GL_TEXTURE_BUFFER, _model->bone_matrices.size() * sizeof(glm::mat4x4), nullptr, GL_STREAM_DRAW); + gl.texBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, _bone_matrices_buffer); + } + + { + OpenGL::Scoped::buffer_binder const binder(_vertices_buffer); + gl.bufferData(GL_ARRAY_BUFFER, _model->_vertices.size() * sizeof(ModelVertex), _model->_vertices.data(), GL_STATIC_DRAW); + } + + { + OpenGL::Scoped::buffer_binder const binder (_box_vbo); + gl.bufferData (GL_ARRAY_BUFFER, _vertex_box_points.size() * sizeof (glm::vec3), _vertex_box_points.data(), GL_STATIC_DRAW); + } + + OpenGL::Scoped::buffer_binder indices_binder(_indices_buffer); + gl.bufferData (GL_ELEMENT_ARRAY_BUFFER, _model->_indices.size() * sizeof(uint16_t), _model->_indices.data(), GL_STATIC_DRAW); + + OpenGL::Scoped::buffer_binder box_indices_binder(_box_indices_buffer); + gl.bufferData (GL_ELEMENT_ARRAY_BUFFER, _box_indices.size() * sizeof(uint16_t), _box_indices.data(), GL_STATIC_DRAW); + + _model->_textureFilenames.clear(); + + _uploaded = true; +} + +void ModelRender::unload() +{ + _model->_textures.clear(); + _buffers.unload(); + _vertex_arrays.unload(); + + if (_bone_matrices_buf_tex) + gl.deleteTextures(1, &_bone_matrices_buf_tex); + + for (auto& particle : _model->_particles) + { + particle.unload(); + } + + for (auto& ribbon : _model->_ribbons) + { + ribbon.unload(); + } + + _uploaded = false; + _vao_setup = false; +} + +void ModelRender::draw(glm::mat4x4 const& model_view + , ModelInstance& instance + , OpenGL::Scoped::use_program& m2_shader + , OpenGL::M2RenderState& model_render_state + , math::frustum const& frustum + , const float& cull_distance + , const glm::vec3& camera + , int animtime + , display_mode display + , bool no_cull +) +{ + if (!_model->finishedLoading() || _model->loading_failed()) + { + return; + } + + if (!no_cull && !instance.isInFrustum(frustum) && !instance.isInRenderDist(cull_distance, camera, display)) + { + return; + } + + if (!_uploaded) + { + upload(); + } + + if (_model->animated && (!_model->animcalc || _model->_per_instance_animation)) + { + _model->animate(model_view, 0, animtime); + _model->animcalc = true; + } + + OpenGL::Scoped::vao_binder const _(_vao); + + m2_shader.uniform("transform", instance.transformMatrix()); + + { + OpenGL::Scoped::buffer_binder const binder(_vertices_buffer); + m2_shader.attrib("pos", 3, GL_FLOAT, GL_FALSE, sizeof(ModelVertex), 0); + m2_shader.attrib("bones_weight", 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (ModelVertex), reinterpret_cast (sizeof (::glm::vec3))); + m2_shader.attrib("bones_indices", 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof (ModelVertex), reinterpret_cast (sizeof (::glm::vec3) + 4)); + m2_shader.attrib("normal", 3, GL_FLOAT, GL_FALSE, sizeof(ModelVertex), reinterpret_cast (sizeof(::glm::vec3) + 8)); + m2_shader.attrib("texcoord1", 2, GL_FLOAT, GL_FALSE, sizeof(ModelVertex), reinterpret_cast (sizeof(::glm::vec3) * 2 + 8)); + m2_shader.attrib("texcoord2", 2, GL_FLOAT, GL_FALSE, sizeof(ModelVertex), reinterpret_cast (sizeof(::glm::vec3) * 2 + 8 + sizeof(glm::vec2))); + } + + OpenGL::Scoped::buffer_binder indices_binder(_indices_buffer); + + for (ModelRenderPass& p : _render_passes) + { + if (p.prepareDraw(m2_shader, _model, model_render_state)) + { + gl.drawElements(GL_TRIANGLES, p.index_count, GL_UNSIGNED_SHORT, reinterpret_cast(p.index_start * sizeof(GLushort))); + p.afterDraw(); + } + } + + gl.disable(GL_BLEND); + gl.enable(GL_CULL_FACE); + gl.depthMask(GL_TRUE); +} + +void ModelRender::draw(glm::mat4x4 const& model_view + , std::vector const& instances + , OpenGL::Scoped::use_program& m2_shader + , OpenGL::M2RenderState& model_render_state + , math::frustum const& frustum + , const float& cull_distance + , const glm::vec3& camera + , int animtime + , bool all_boxes + , std::unordered_map& model_boxes_to_draw + , display_mode display + , bool no_cull +) +{ + ZoneScopedN(NOGGIT_CURRENT_FUNCTION); + + { + ZoneScopedN("Model::draw() : uploads") + + if (!_model->finishedLoading() || _model->loading_failed()) + { + return; + } + + if (!_uploaded) + { + upload(); + } + + if (!_vao_setup) + { + setupVAO(m2_shader); + } + } + + if (instances.empty()) + { + return; + } + + { + ZoneScopedN("Model::draw() : drawing") + + if (_model->animated && (!_model->animcalc || _model->_per_instance_animation)) + { + _model->animate(model_view, 0, animtime); + _model->animcalc = true; + } + + // store the model count to draw the bounding boxes later + if (all_boxes || _model->_hidden) + { + model_boxes_to_draw.emplace(_model, instances.size()); + } + + /* + if (draw_particles && (!_particles.empty() || !_ribbons.empty())) + { + models_with_particles.emplace(this, n_visible_instances); + } + */ + + OpenGL::Scoped::vao_binder const _ (_vao); + + { + OpenGL::Scoped::buffer_binder const transform_binder (_transform_buffer); + gl.bufferData(GL_ARRAY_BUFFER, instances.size() * sizeof(::glm::mat4x4), instances.data(), GL_DYNAMIC_DRAW); + //m2_shader.attrib("transform", 0, 1); + } + + if (_model->animBones) + { + gl.activeTexture(GL_TEXTURE0); + gl.bindTexture(GL_TEXTURE_BUFFER, _bone_matrices_buf_tex); + m2_shader.uniform("anim_bones", true); + } + else + { + m2_shader.uniform("anim_bones", false); + } + + OpenGL::Scoped::buffer_binder indices_binder(_indices_buffer); + + for (ModelRenderPass& p : _render_passes) + { + if (p.prepareDraw(m2_shader, _model, model_render_state)) + { + gl.drawElementsInstanced(GL_TRIANGLES, p.index_count, GL_UNSIGNED_SHORT, reinterpret_cast(p.index_start * sizeof(GLushort)), instances.size()); + //p.after_draw(); + } + } + } + +} + +void ModelRender::drawParticles(glm::mat4x4 const& model_view + , OpenGL::Scoped::use_program& particles_shader + , std::size_t instance_count +) +{ + for (auto& p : _model->_particles) + { + p.draw(model_view, particles_shader, _transform_buffer, instance_count); + } +} + +void ModelRender::drawRibbons( OpenGL::Scoped::use_program& ribbons_shader + , std::size_t instance_count +) +{ + for (auto& r : _model->_ribbons) + { + r.draw(ribbons_shader, _transform_buffer, instance_count); + } +} + +void ModelRender::drawBox(OpenGL::Scoped::use_program& m2_box_shader, std::size_t box_count) +{ + + OpenGL::Scoped::vao_binder const _ (_box_vao); + + { + OpenGL::Scoped::buffer_binder const transform_binder (_transform_buffer); + m2_box_shader.attrib("transform", 0, 1); + } + + { + OpenGL::Scoped::buffer_binder const binder (_box_vbo); + m2_box_shader.attrib("position", 3, GL_FLOAT, GL_FALSE, 0, 0); + } + + OpenGL::Scoped::buffer_binder indices_binder(_box_indices_buffer); + + gl.drawElementsInstanced (GL_LINE_STRIP, _box_indices.size(), GL_UNSIGNED_SHORT, nullptr, box_count); +} + +void ModelRender::setupVAO(OpenGL::Scoped::use_program& m2_shader) +{ + OpenGL::Scoped::vao_binder const _(_vao); + + { + OpenGL::Scoped::buffer_binder const binder (_vertices_buffer); + m2_shader.attrib("pos", 3, GL_FLOAT, GL_FALSE, sizeof (ModelVertex), 0); + m2_shader.attribi("bones_weight", 4, GL_UNSIGNED_BYTE, sizeof (ModelVertex), reinterpret_cast (sizeof (::glm::vec3))); + m2_shader.attribi("bones_indices",4, GL_UNSIGNED_BYTE, sizeof (ModelVertex), reinterpret_cast (sizeof (::glm::vec3) + 4)); + m2_shader.attrib("normal", 3, GL_FLOAT, GL_FALSE, sizeof (ModelVertex), reinterpret_cast (sizeof (::glm::vec3) + 8)); + m2_shader.attrib("texcoord1", 2, GL_FLOAT, GL_FALSE, sizeof (ModelVertex), reinterpret_cast (sizeof (::glm::vec3) * 2 + 8)); + m2_shader.attrib("texcoord2", 2, GL_FLOAT, GL_FALSE, sizeof (ModelVertex), reinterpret_cast (sizeof (::glm::vec3) * 2 + 8 + sizeof(glm::vec2))); + } + + { + OpenGL::Scoped::buffer_binder const transform_binder (_transform_buffer); + gl.bufferData(GL_ARRAY_BUFFER, 10 * sizeof(::glm::mat4x4), nullptr, GL_DYNAMIC_DRAW); + m2_shader.attrib("transform", 0, 1); + } + + _vao_setup = true; +} + + +void ModelRender::fixShaderIdBlendOverride() +{ + for (auto& pass : _render_passes) + { + if (pass.shader_id & 0x8000) + { + continue; + } + + int shader = 0; + bool blend_mode_override = (_model->header.Flags & 8); + + // fuckporting check + if (pass.texture_coord_combo_index + pass.texture_count - 1 >= _model->_texture_unit_lookup.size()) + { + LogDebug << "wrong texture coord combo index on fuckported model: " << _model->_file_key.stringRepr() << std::endl; + // use default stuff + pass.shader_id = 0; + pass.texture_count = 1; + + continue; + } + + if (!blend_mode_override) + { + uint16_t texture_unit_lookup = _model->_texture_unit_lookup[pass.texture_coord_combo_index]; + + if (_model->_render_flags[pass.renderflag_index].blend) + { + shader = 1; + + if (texture_unit_lookup == 0xFFFF) + { + shader |= 0x8; + } + } + + shader <<= 4; + + if (texture_unit_lookup == 1) + { + shader |= 0x4000; + } + } + else + { + uint16_t runtime_shader_val[2] = { 0, 0 }; + + for (int i = 0; i < pass.texture_count; ++i) + { + uint16_t override_blend = _model->blend_override[pass.shader_id + i]; + uint16_t texture_unit_lookup = _model->_texture_unit_lookup[pass.texture_coord_combo_index + i]; + + if (i == 0 && _model->_render_flags[pass.renderflag_index].blend == 0) + { + override_blend = 0; + } + + runtime_shader_val[i] = override_blend; + + if (texture_unit_lookup == 0xFFFF) + { + runtime_shader_val[i] |= 0x8; + } + + if (texture_unit_lookup == 1 && i + 1 == pass.texture_count) + { + shader |= 0x4000; + } + } + + shader |= (runtime_shader_val[1] & 0xFFFF) | ((runtime_shader_val[0] << 4) & 0xFFFF); + } + + pass.shader_id = shader; + } +} + +void ModelRender::fixShaderIDLayer() +{ + int non_layered_count = 0; + + for (auto const& pass : _render_passes) + { + if (pass.material_layer <= 0) + { + non_layered_count++; + } + } + + if (non_layered_count < _render_passes.size()) + { + std::vector passes; + + ModelRenderPass* first_pass = nullptr; + bool need_reducing = false; + uint16_t previous_render_flag = -1, some_flags = 0; + + for (auto& pass : _render_passes) + { + if (pass.renderflag_index == previous_render_flag) + { + need_reducing = true; + continue; + } + + previous_render_flag = pass.renderflag_index; + + uint8_t lower_bits = pass.shader_id & 0x7; + + if (pass.material_layer == 0) + { + if (pass.texture_count >= 1 && _model->_render_flags[pass.renderflag_index].blend == 0) + { + pass.shader_id &= 0xFF8F; + } + + first_pass = &pass; + } + + bool xor_unlit = ((_model->_render_flags[pass.renderflag_index].flags.unlit ^ _model->_render_flags[first_pass->renderflag_index].flags.unlit) & 1) == 0; + + if ((some_flags & 0xFF) == 1) + { + if ((_model->_render_flags[pass.renderflag_index].blend == 1 || _model->_render_flags[pass.renderflag_index].blend == 2) + && pass.texture_count == 1 + && xor_unlit + && pass.texture_combo_index == first_pass->texture_combo_index + ) + { + if (_model->_transparency_lookup[pass.transparency_combo_index] == _model->_transparency_lookup[first_pass->transparency_combo_index]) + { + pass.shader_id = 0x8000; + first_pass->shader_id = 0x8001; + + some_flags = (some_flags & 0xFF00) | 3; + + // current pass removed (not needed) + continue; + } + } + + some_flags = (some_flags & 0xFF00); + } + + int16_t texture_unit_lookup = _model->_texture_unit_lookup[pass.texture_coord_combo_index]; + + if ((some_flags & 0xFF) < 2) + { + if ((_model->_render_flags[pass.renderflag_index].blend == 0) && (pass.texture_count == 2) && ((lower_bits == 4) || (lower_bits == 6))) + { + if (texture_unit_lookup == 0 && (_model->_texture_unit_lookup[pass.texture_coord_combo_index + 1] == -1)) + { + some_flags = (some_flags & 0xFF00) | 1; + } + } + } + + if ((some_flags >> 8) != 0) + { + if ((some_flags >> 8) == 1) + { + if (((_model->_render_flags[pass.renderflag_index].blend != 4) && (_model->_render_flags[pass.renderflag_index].blend != 6)) || (pass.texture_count != 1) || (texture_unit_lookup >= 0)) + { + some_flags &= 0xFF00; + } + else if (_model->_transparency_lookup[pass.transparency_combo_index] == _model->_transparency_lookup[first_pass->transparency_combo_index]) + { + pass.shader_id = 0x8000; + first_pass->shader_id = _model->_render_flags[pass.renderflag_index].blend != 4 ? 0xE : 0x8002; + + some_flags = (some_flags & 0xFF) | (2 << 8); + + first_pass->texture_count = 2; + + first_pass->textures[1] = pass.texture_combo_index; + first_pass->uv_animations[1] = pass.animation_combo_index; + + // current pass removed (merged with the previous one) + continue; + } + } + else + { + if ((some_flags >> 8) != 2) + { + continue; + } + + if ( ((_model->_render_flags[pass.renderflag_index].blend != 2) && (_model->_render_flags[pass.renderflag_index].blend != 1)) + || (pass.texture_count != 1) + || xor_unlit + || ((pass.texture_combo_index & 0xff) != (first_pass->texture_combo_index & 0xff)) + ) + { + some_flags &= 0xFF00; + } + else if (_model->_transparency_lookup[pass.transparency_combo_index] == _model->_transparency_lookup[first_pass->transparency_combo_index]) + { + // current pass ignored/removed + pass.shader_id = 0x8000; + first_pass->shader_id = ((first_pass->shader_id == 0x8002 ? 2 : 0) - 0x7FFF) & 0xFFFF; + some_flags = (some_flags & 0xFF) | (3 << 8); + continue; + } + } + some_flags = (some_flags & 0xFF); + } + + if ((_model->_render_flags[pass.renderflag_index].blend == 0) && (pass.texture_count == 1) && (texture_unit_lookup == 0)) + { + some_flags = (some_flags & 0xFF) | (1 << 8); + } + + // setup texture and anim lookup indices + pass.textures[0] = pass.texture_combo_index; + pass.textures[1] = pass.texture_count > 1 ? pass.texture_combo_index + 1 : 0; + pass.uv_animations[0] = pass.animation_combo_index; + pass.uv_animations[1] = pass.texture_count > 1 ? pass.animation_combo_index + 1 : 0; + + passes.push_back(pass); + } + + if (need_reducing) + { + previous_render_flag = -1; + for (int i = 0; i < passes.size(); ++i) + { + auto& pass = _render_passes[i]; + uint16_t renderflag_index = pass.renderflag_index; + + if (renderflag_index == previous_render_flag) + { + pass.shader_id = _render_passes[i - 1].shader_id; + pass.texture_count = _render_passes[i - 1].texture_count; + pass.texture_combo_index = _render_passes[i - 1].texture_combo_index; + pass.texture_coord_combo_index = _render_passes[i - 1].texture_coord_combo_index; + } + else + { + previous_render_flag = renderflag_index; + } + } + } + + _render_passes = passes; + } + // no layering, just setting some infos + else + { + for (auto& pass : _render_passes) + { + pass.textures[0] = pass.texture_combo_index; + pass.textures[1] = pass.texture_count > 1 ? pass.texture_combo_index + 1 : 0; + pass.uv_animations[0] = pass.animation_combo_index; + pass.uv_animations[1] = pass.texture_count > 1 ? pass.animation_combo_index + 1 : 0; + } + } +} + +namespace +{ + +// https://wowdev.wiki/M2/.skin/WotLK_shader_selection + std::optional GetPixelShader(uint16_t texture_count, uint16_t shader_id) + { + uint16_t texture1_fragment_mode = (shader_id >> 4) & 7; + uint16_t texture2_fragment_mode = shader_id & 7; + // uint16_t texture1_env_map = (shader_id >> 4) & 8; + // uint16_t texture2_env_map = shader_id & 8; + + std::optional pixel_shader; + + if (texture_count == 1) + { + switch (texture1_fragment_mode) + { + case 0: + pixel_shader = ModelPixelShader::Combiners_Opaque; + break; + case 2: + pixel_shader = ModelPixelShader::Combiners_Decal; + break; + case 3: + pixel_shader = ModelPixelShader::Combiners_Add; + break; + case 4: + pixel_shader = ModelPixelShader::Combiners_Mod2x; + break; + case 5: + pixel_shader = ModelPixelShader::Combiners_Fade; + break; + default: + pixel_shader = ModelPixelShader::Combiners_Mod; + break; + } + } + else + { + if (!texture1_fragment_mode) + { + switch (texture2_fragment_mode) + { + case 0: + pixel_shader = ModelPixelShader::Combiners_Opaque_Opaque; + break; + case 3: + pixel_shader = ModelPixelShader::Combiners_Opaque_Add; + break; + case 4: + pixel_shader = ModelPixelShader::Combiners_Opaque_Mod2x; + break; + case 6: + pixel_shader = ModelPixelShader::Combiners_Opaque_Mod2xNA; + break; + case 7: + pixel_shader = ModelPixelShader::Combiners_Opaque_AddNA; + break; + default: + pixel_shader = ModelPixelShader::Combiners_Opaque_Mod; + break; + } + } + else if (texture1_fragment_mode == 1) + { + switch (texture2_fragment_mode) + { + case 0: + pixel_shader = ModelPixelShader::Combiners_Mod_Opaque; + break; + case 3: + pixel_shader = ModelPixelShader::Combiners_Mod_Add; + break; + case 4: + pixel_shader = ModelPixelShader::Combiners_Mod_Mod2x; + break; + case 6: + pixel_shader = ModelPixelShader::Combiners_Mod_Mod2xNA; + break; + case 7: + pixel_shader = ModelPixelShader::Combiners_Mod_AddNA; + break; + default: + pixel_shader = ModelPixelShader::Combiners_Mod_Mod; + break; + } + } + else if (texture1_fragment_mode == 3) + { + if (texture2_fragment_mode == 1) + { + pixel_shader = ModelPixelShader::Combiners_Add_Mod; + } + } + else if (texture1_fragment_mode == 4 && texture2_fragment_mode == 4) + { + pixel_shader = ModelPixelShader::Combiners_Mod2x_Mod2x; + } + else if (texture2_fragment_mode == 1) + { + pixel_shader = ModelPixelShader::Combiners_Mod_Mod2x; + } + } + + + return pixel_shader; + } + + std::optional M2GetPixelShaderID (uint16_t texture_count, uint16_t shader_id) + { + std::optional pixel_shader; + + if (!(shader_id & 0x8000)) + { + pixel_shader = GetPixelShader(texture_count, shader_id); + + if (!pixel_shader) + { + pixel_shader = GetPixelShader(texture_count, 0x11); + } + } + else + { + switch (shader_id & 0x7FFF) + { + case 1: + pixel_shader = ModelPixelShader::Combiners_Opaque_Mod2xNA_Alpha; + break; + case 2: + pixel_shader = ModelPixelShader::Combiners_Opaque_AddAlpha; + break; + case 3: + pixel_shader = ModelPixelShader::Combiners_Opaque_AddAlpha_Alpha; + break; + } + } + + return pixel_shader; + } +} + +void ModelRender::computePixelShaderIDs() +{ + for (auto& pass : _render_passes) + { + pass.pixel_shader = M2GetPixelShaderID(pass.texture_count, pass.shader_id); + } +} + +void ModelRender::initRenderPasses(ModelView const* view, ModelTexUnit const* tex_unit, ModelGeoset const* model_geosets) +{ + _render_passes.reserve(view->n_texture_unit); + for (size_t j = 0; jn_texture_unit; j++) + { + size_t geoset = tex_unit[j].submesh; + + ModelRenderPass pass(tex_unit[j], _model); + pass.ordering_thingy = model_geosets[geoset].BoundingBox[0].x; + + pass.index_start = model_geosets[geoset].istart; + pass.index_count = model_geosets[geoset].icount; + pass.vertex_start = model_geosets[geoset].vstart; + pass.vertex_end = pass.vertex_start + model_geosets[geoset].vcount; + + _render_passes.push_back(std::move(pass)); + } + + + fixShaderIdBlendOverride(); + fixShaderIDLayer(); + computePixelShaderIDs(); + + + for (auto& pass : _render_passes) + { + pass.initUVTypes(_model); + } + + // transparent parts come later + std::sort(_render_passes.begin(), _render_passes.end()); +} + +void ModelRender::updateBoneMatrices() +{ + { + OpenGL::Scoped::buffer_binder const binder (_bone_matrices_buffer); + gl.bufferSubData(GL_TEXTURE_BUFFER, 0, _model->bone_matrices.size() * sizeof(glm::mat4x4), _model->bone_matrices.data()); + } +} + +// ModelRenderPass + + +ModelRenderPass::ModelRenderPass(ModelTexUnit const& tex_unit, Model* m) + : ModelTexUnit(tex_unit) + , blend_mode(m->_render_flags[renderflag_index].blend) +{ +} + +bool ModelRenderPass::prepareDraw(OpenGL::Scoped::use_program& m2_shader, Model *m, OpenGL::M2RenderState& model_render_state) +{ + if (!m->showGeosets[submesh] || !pixel_shader) + { + return false; + } + + // COLOUR + // Get the colour and transparency and check that we should even render + glm::vec4 mesh_color = glm::vec4(1.0f, 1.0f, 1.0f, m->trans); // ?? + glm::vec4 emissive_color = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); + + auto const& renderflag(m->_render_flags[renderflag_index]); + + // emissive colors + if (color_index != -1 && m->_colors[color_index].color.uses(0)) + { + ::glm::vec3 c (m->_colors[color_index].color.getValue (0, m->_anim_time, m->_global_animtime)); + if (m->_colors[color_index].opacity.uses (m->_current_anim_seq)) + { + mesh_color.w = m->_colors[color_index].opacity.getValue (m->_current_anim_seq, m->_anim_time, m->_global_animtime); + } + + mesh_color.x = c.x; mesh_color.y = c.y; mesh_color.z = c.z; + + emissive_color = glm::vec4(c.x,c.y,c.z, mesh_color.w); + } + + // opacity + if (transparency_combo_index != 0xFFFF && transparency_combo_index < m->_transparency_lookup.size()) + { + auto& transparency (m->_transparency[m->_transparency_lookup[transparency_combo_index]].trans); + if (transparency.uses (0)) + { + mesh_color.w = mesh_color.w * transparency.getValue(0, m->_anim_time, m->_global_animtime); + } + } + + // exit and return false before affecting the opengl render state + if (!((mesh_color.w > 0) && (color_index == -1 || emissive_color.w > 0))) + { + return false; + } + + + if (model_render_state.blend != renderflag.blend) + { + switch (static_cast(renderflag.blend)) + { + default: + case M2Blend::Opaque: + case M2Blend::Alpha_Key: + gl.disable(GL_BLEND); + break; + case M2Blend::Alpha: + gl.enable(GL_BLEND); + gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case M2Blend::No_Add_Alpha: + gl.enable(GL_BLEND); + gl.blendFunc(GL_ONE, GL_ONE); + break; + case M2Blend::Add: + gl.enable(GL_BLEND); + gl.blendFunc(GL_SRC_ALPHA, GL_ONE); + break; + case M2Blend::Mod: + gl.enable(GL_BLEND); + gl.blendFunc(GL_DST_COLOR, GL_ZERO); + break; + case M2Blend::Mod2x: + gl.enable(GL_BLEND); + gl.blendFunc(GL_DST_COLOR, GL_SRC_COLOR); + break; + } + + m2_shader.uniform("blend_mode", static_cast(renderflag.blend)); + model_render_state.blend = renderflag.blend; + } + + if (model_render_state.backface_cull != !renderflag.flags.two_sided) + { + if (renderflag.flags.two_sided) + { + gl.disable(GL_CULL_FACE); + } + else + { + gl.enable(GL_CULL_FACE); + } + + model_render_state.backface_cull = !renderflag.flags.two_sided; + } + + if (model_render_state.z_buffered != renderflag.flags.z_buffered) + { + if (renderflag.flags.z_buffered) + { + gl.depthMask(GL_FALSE); + } + else + { + gl.depthMask(GL_TRUE); + } + + model_render_state.z_buffered = renderflag.flags.z_buffered; + } + + if (model_render_state.unfogged != renderflag.flags.unfogged) + { + m2_shader.uniform("unfogged", (int)renderflag.flags.unfogged); + model_render_state.unfogged = renderflag.flags.unfogged; + } + + if (model_render_state.unlit != renderflag.flags.unlit) + { + m2_shader.uniform("unlit", (int)renderflag.flags.unlit); + model_render_state.unlit = renderflag.flags.unlit; + } + + if (texture_count > 1) + { + bindTexture(1, m, model_render_state, m2_shader); + } + + bindTexture(0, m, model_render_state, m2_shader); + + GLint tu1 = static_cast(tu_lookups[0]), tu2 = static_cast(tu_lookups[1]); + + if (model_render_state.tex_unit_lookups[0] != tu1) + { + m2_shader.uniform("tex_unit_lookup_1", tu1); + model_render_state.tex_unit_lookups[0] = tu1; + } + + if (model_render_state.tex_unit_lookups[1] != tu2) + { + m2_shader.uniform("tex_unit_lookup_2", tu2); + model_render_state.tex_unit_lookups[1] = tu2; + } + + int16_t tex_anim_lookup = m->_texture_animation_lookups[uv_animations[0]]; + static const glm::mat4x4 unit(glm::mat4x4(1)); + + if (tex_anim_lookup != -1) + { + m2_shader.uniform("tex_matrix_1", m->_texture_animations[tex_anim_lookup].mat); + if (texture_count > 1) + { + tex_anim_lookup = m->_texture_animation_lookups[uv_animations[1]]; + if (tex_anim_lookup != -1) + { + m2_shader.uniform("tex_matrix_2", m->_texture_animations[tex_anim_lookup].mat); + } + else + { + m2_shader.uniform("tex_matrix_2", unit); + } + } + } + else + { + m2_shader.uniform("tex_matrix_1", unit); + m2_shader.uniform("tex_matrix_2", unit); + } + + + GLint ps = static_cast(pixel_shader.value()); + if (model_render_state.pixel_shader != ps) + { + m2_shader.uniform("pixel_shader", ps); + model_render_state.pixel_shader = ps; + } + + m2_shader.uniform("mesh_color", mesh_color); + + return true; +} + +void ModelRenderPass::afterDraw() +{ + gl.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +} + +void ModelRenderPass::bindTexture(size_t index, Model* m, OpenGL::M2RenderState& model_render_state, OpenGL::Scoped::use_program& m2_shader) +{ + + uint16_t tex = m->_texture_lookup[textures[index]]; + + if (m->_specialTextures[tex] == -1) + { + auto& texture = m->_textures[tex]; + texture->upload(); + GLuint tex_array = texture->texture_array(); + int tex_index = texture->array_index(); + + gl.activeTexture(GL_TEXTURE0 + index + 1); + gl.bindTexture(GL_TEXTURE_2D_ARRAY, tex_array); + /* + if (model_render_state.tex_arrays[index] != tex_array) + { + gl.activeTexture(GL_TEXTURE0 + index + 1); + gl.bindTexture(GL_TEXTURE_2D_ARRAY, tex_array); + model_render_state.tex_arrays[index] = tex_array; + } + + + if (model_render_state.tex_indices[index] != tex_index) + { + m2_shader.uniform(index ? "tex2_index" : "tex1_index", tex_index); + model_render_state.tex_indices[index] = tex_index; + } + + */ + + m2_shader.uniform(index ? "tex2_index" : "tex1_index", tex_index); + model_render_state.tex_indices[index] = tex_index; + + } + else + { + auto& texture = m->_replaceTextures.at (m->_specialTextures[tex]); + texture->upload(); + GLuint tex_array = texture->texture_array(); + int tex_index = texture->array_index(); + + gl.activeTexture(GL_TEXTURE0 + index + 1); + gl.bindTexture(GL_TEXTURE_2D_ARRAY, tex_array); + + /* + if (model_render_state.tex_arrays[index] != tex_array) + { + gl.activeTexture(GL_TEXTURE0 + index + 1); + gl.bindTexture(GL_TEXTURE_2D_ARRAY, tex_array); + model_render_state.tex_arrays[index] = tex_array; + } + + if (model_render_state.tex_indices[index] != tex_index) + { + m2_shader.uniform(index ? "tex2_index" : "tex1_index", tex_index); + model_render_state.tex_indices[index] = tex_index; + } + + */ + + m2_shader.uniform(index ? "tex2_index" : "tex1_index", tex_index); + model_render_state.tex_indices[index] = tex_index; + + } +} + +void ModelRenderPass::initUVTypes(Model* m) +{ + tu_lookups[0] = texture_unit_lookup::none; + tu_lookups[1] = texture_unit_lookup::none; + + if (m->_texture_unit_lookup.size() < texture_coord_combo_index + texture_count) + { + LogError << "model: texture_coord_combo_index out of range " << m->file_key().stringRepr() << std::endl; + + for (int i = 0; i < texture_count; ++i) + { + switch (i) + { + case 0: tu_lookups[i] = texture_unit_lookup::t1; break; + case 1: tu_lookups[i] = texture_unit_lookup::t2; break; + } + } + + return; + + //throw std::out_of_range("model: texture_coord_combo_index out of range " + m->filename); + } + + for (int i = 0; i < texture_count; ++i) + { + switch (m->_texture_unit_lookup[texture_coord_combo_index + i]) + { + case (int16_t)(-1): tu_lookups[i] = texture_unit_lookup::environment; break; + case 0: tu_lookups[i] = texture_unit_lookup::t1; break; + case 1: tu_lookups[i] = texture_unit_lookup::t2; break; + } + } +} diff --git a/src/noggit/rendering/ModelRender.hpp b/src/noggit/rendering/ModelRender.hpp new file mode 100644 index 00000000..68bb9fef --- /dev/null +++ b/src/noggit/rendering/ModelRender.hpp @@ -0,0 +1,190 @@ +// This file is part of Noggit3, licensed under GNU General Public License (version 3). + +#ifndef NOGGIT_MODELRENDER_HPP +#define NOGGIT_MODELRENDER_HPP + +#include +#include +#include +#include +#include +#include + +class Model; +class ModelInstance; + +namespace Noggit::Rendering +{ + enum class M2Blend : uint16_t + { + Opaque, + Alpha_Key, + Alpha, + No_Add_Alpha, + Add, + Mod, + Mod2x + }; + + enum class ModelPixelShader : uint16_t + { + Combiners_Opaque, + Combiners_Decal, + Combiners_Add, + Combiners_Mod2x, + Combiners_Fade, + Combiners_Mod, + Combiners_Opaque_Opaque, + Combiners_Opaque_Add, + Combiners_Opaque_Mod2x, + Combiners_Opaque_Mod2xNA, + Combiners_Opaque_AddNA, + Combiners_Opaque_Mod, + Combiners_Mod_Opaque, + Combiners_Mod_Add, + Combiners_Mod_Mod2x, + Combiners_Mod_Mod2xNA, + Combiners_Mod_AddNA, + Combiners_Mod_Mod, + Combiners_Add_Mod, + Combiners_Mod2x_Mod2x, + Combiners_Opaque_Mod2xNA_Alpha, + Combiners_Opaque_AddAlpha, + Combiners_Opaque_AddAlpha_Alpha, + }; + + enum class texture_unit_lookup : int + { + environment, + t1, + t2, + none + }; + + + struct ModelRenderPass : ModelTexUnit + { + ModelRenderPass() = delete; + ModelRenderPass(ModelTexUnit const& tex_unit, Model* m); + + float ordering_thingy = 0.f; + uint16_t index_start = 0, index_count = 0, vertex_start = 0, vertex_end = 0; + uint16_t blend_mode = 0; + texture_unit_lookup tu_lookups[2]; + uint16_t textures[2]; + uint16_t uv_animations[2]; + std::optional pixel_shader; + + + bool prepareDraw(OpenGL::Scoped::use_program& m2_shader, Model *m, OpenGL::M2RenderState& model_render_state); + void afterDraw(); + void bindTexture(size_t index, Model* m, OpenGL::M2RenderState& model_render_state, OpenGL::Scoped::use_program& m2_shader); + void initUVTypes(Model* m); + + bool operator< (const ModelRenderPass &m) const + { + if (priority_plane < m.priority_plane) + { + return true; + } + else if (priority_plane > m.priority_plane) + { + return false; + } + else + { + return blend_mode == m.blend_mode ? (ordering_thingy < m.ordering_thingy) : blend_mode < m.blend_mode; + } + } + }; + + class ModelRender : public BaseRender + { + friend struct ModelRenderPass; + + public: + ModelRender(Model* model); + + void upload() override; + void unload() override; + + void draw(glm::mat4x4 const& model_view + , ModelInstance& instance + , OpenGL::Scoped::use_program& m2_shader + , OpenGL::M2RenderState& model_render_state + , math::frustum const& frustum + , const float& cull_distance + , const glm::vec3& camera + , int animtime + , display_mode display + , bool no_cull = false + ); + + void draw (glm::mat4x4 const& model_view + , std::vector const& instances + , OpenGL::Scoped::use_program& m2_shader + , OpenGL::M2RenderState& model_render_state + , math::frustum const& frustum + , const float& cull_distance + , const glm::vec3& camera + , int animtime + , bool all_boxes + , std::unordered_map& model_boxes_to_draw + , display_mode display + , bool no_cull = false + ); + + void drawParticles(glm::mat4x4 const& model_view + , OpenGL::Scoped::use_program& particles_shader + , std::size_t instance_count + ); + + void drawRibbons(OpenGL::Scoped::use_program& ribbons_shader + , std::size_t instance_count + ); + + void drawBox(OpenGL::Scoped::use_program& m2_box_shader, std::size_t box_count); + + [[nodiscard]] + std::vector const& renderPasses() const { return _render_passes; }; + + void updateBoneMatrices(); + + void initRenderPasses(ModelView const* view, ModelTexUnit const* tex_unit, ModelGeoset const* model_geosets); + + private: + + void setupVAO(OpenGL::Scoped::use_program& m2_shader); + void fixShaderIdBlendOverride(); + void fixShaderIDLayer(); + void computePixelShaderIDs(); + + + Model* _model; + + // buffers + OpenGL::Scoped::deferred_upload_buffers<6> _buffers; + OpenGL::Scoped::deferred_upload_vertex_arrays<2> _vertex_arrays; + + std::vector const _box_indices = {5, 7, 3, 2, 0, 1, 3, 1, 5, 4, 0, 4, 6, 2, 6, 7}; + + GLuint const& _vao = _vertex_arrays[0]; + GLuint const& _transform_buffer = _buffers[0]; + GLuint const& _vertices_buffer = _buffers[1]; + GLuint const& _indices_buffer = _buffers[3]; + GLuint const& _box_indices_buffer = _buffers[4]; + GLuint const& _bone_matrices_buffer = _buffers[5]; + + GLuint const& _box_vao = _vertex_arrays[1]; + GLuint const& _box_vbo = _buffers[2]; + + GLuint _bone_matrices_buf_tex; + std::vector _vertex_box_points; + std::vector _render_passes; + + bool _uploaded = false; + bool _vao_setup = false; + }; +} + +#endif //NOGGIT_MODELRENDER_HPP diff --git a/src/noggit/rendering/WMORender.cpp b/src/noggit/rendering/WMORender.cpp index fb27c66f..86084e70 100644 --- a/src/noggit/rendering/WMORender.cpp +++ b/src/noggit/rendering/WMORender.cpp @@ -148,7 +148,7 @@ bool WMORender::drawSkybox(const glm::mat4x4& model_view, const glm::vec3& camer m2_shader.uniform("tex_unit_lookup_2", 0); m2_shader.uniform("pixel_shader", 0); - _wmo->skybox->get()->draw(model_view, sky, m2_shader, model_render_state, frustum, cull_distance, camera_pos, animtime, display_mode::in_3D); + _wmo->skybox->get()->renderer()->draw(model_view, sky, m2_shader, model_render_state, frustum, cull_distance, camera_pos, animtime, display_mode::in_3D); return true; } diff --git a/src/noggit/rendering/WorldRender.cpp b/src/noggit/rendering/WorldRender.cpp index 68c54e88..173d6f22 100644 --- a/src/noggit/rendering/WorldRender.cpp +++ b/src/noggit/rendering/WorldRender.cpp @@ -651,7 +651,7 @@ void WorldRender::draw (glm::mat4x4 const& model_view if (draw_hidden_models || !pair.first->is_hidden()) { - pair.first->draw( model_view + pair.first->renderer()->draw( model_view , pair.second , m2_shader , model_render_state @@ -731,7 +731,7 @@ void WorldRender::draw (glm::mat4x4 const& model_view ; m2_box_shader.uniform("color", color); - it.first->draw_box(m2_box_shader, it.second); + it.first->renderer()->drawBox(m2_box_shader, it.second); } } diff --git a/src/noggit/ui/tools/PreviewRenderer/PreviewRenderer.cpp b/src/noggit/ui/tools/PreviewRenderer/PreviewRenderer.cpp index 919f5c99..4635e376 100644 --- a/src/noggit/ui/tools/PreviewRenderer/PreviewRenderer.cpp +++ b/src/noggit/ui/tools/PreviewRenderer/PreviewRenderer.cpp @@ -245,7 +245,7 @@ void PreviewRenderer::draw() instance[0] = &model_instance; instance_mtx[0] = model_instance.transformMatrix(); - model_instance.model->draw( + model_instance.model->renderer()->draw( mv , instance_mtx , m2_shader @@ -269,7 +269,7 @@ void PreviewRenderer::draw() instance_mtx.push_back(instance->transformMatrix()); } - it.second[0]->model->draw( + it.second[0]->model->renderer()->draw( mv , instance_mtx , m2_shader @@ -303,7 +303,7 @@ void PreviewRenderer::draw() ; m2_box_shader.uniform("color", color); - it.first->draw_box(m2_box_shader, it.second); + it.first->renderer()->drawBox(m2_box_shader, it.second); } } }