#include "render_state.h" namespace mt_host { std::array multiply(const float* a, const float* b) { std::array result{}; for (int row = 0; row < 4; ++row) for (int col = 0; col < 4; ++col) for (int k = 0; k < 4; ++k) result[row * 4 + col] += a[row * 4 + k] * b[k * 4 + col]; return result; } std::array draw_mvp(const Render3DDraw& draw) { const auto world_view = multiply(draw.world, draw.view); return multiply(world_view.data(), draw.proj); } std::array unpack_argb(std::uint32_t argb) { return { float((argb >> 16) & 255) / 255.0f, float((argb >> 8) & 255) / 255.0f, float(argb & 255) / 255.0f, float((argb >> 24) & 255) / 255.0f}; } // Inverse transpose of the upper 3x3 of a row-vector matrix, the D3D8 normal transform. std::array normal_matrix(const std::array& m) { const float a = m[0], b = m[1], c = m[2], d = m[4], e = m[5], f = m[6], g = m[8], h = m[9], i = m[10]; const float A = e * i - f * h, B = -(d * i - f * g), C = d * h - e * g; const float D = -(b * i - c * h), E = a * i - c * g, F = -(a * h - b * g); const float G = b * f - c * e, H = -(a * f - c * d), I = a * e - b * d; const float det = a * A + b * B + c * C; std::array out = kIdentityMatrix; if (det == 0.0f) return out; const float s = 1.0f / det; // inverse = adjugate / det; its transpose is the cofactor matrix / det. out[0] = A * s; out[1] = B * s; out[2] = C * s; out[4] = D * s; out[5] = E * s; out[6] = F * s; out[8] = G * s; out[9] = H * s; out[10] = I * s; return out; } PushConstants make_push_constants(const Render3DDraw& draw, float skin_offset_encoded) { PushConstants constants{}; constants.mvp = draw_mvp(draw); constants.params = {skin_offset_encoded, draw.pretransformed ? 1.0f : 0.0f, 0.0f, 0.0f}; return constants; } FixedFunctionState make_fixed_function_state(const Render3DDraw& draw) { FixedFunctionState state{}; for (int stage = 0; stage < 2; ++stage) { state.stage_color[stage] = {draw.color_op[stage], draw.color_arg1[stage], draw.color_arg2[stage], draw.texcoord_index[stage]}; state.stage_alpha[stage] = {draw.alpha_op[stage], draw.alpha_arg1[stage], draw.alpha_arg2[stage], draw.texture_transform_flags[stage]}; std::copy(draw.texture_matrix[stage], draw.texture_matrix[stage] + 16, state.texture_matrix[stage].begin()); } state.texture_factor = unpack_argb(draw.texture_factor); state.world_view = multiply(draw.world, draw.view); state.normal_matrix = normal_matrix(state.world_view); state.fog_color = unpack_argb(draw.fog_color); state.fog_params = {draw.fog_start, draw.fog_end, draw.fog_density, draw.fog_range_enable ? 1.0f : 0.0f}; const std::uint32_t fog_mode = draw.fog_enable ? (draw.fog_table_mode ? draw.fog_table_mode : (draw.pretransformed ? 4u : draw.fog_vertex_mode)) : 0; state.flags = {1u, !draw.texture0.empty() ? 1u : 0u, !draw.texture1.empty() ? 1u : 0u, fog_mode}; state.lighting_flags = {draw.lighting, draw.color_vertex, draw.diffuse.empty() ? 0u : 1u, draw.normalize_normals}; state.material_sources = {draw.diffuse_material_source, draw.ambient_material_source, draw.emissive_material_source, draw.local_viewer}; state.alpha_test = {draw.alpha_test, draw.alpha_func, draw.alpha_ref, 0}; std::copy(draw.material_diffuse, draw.material_diffuse + 4, state.material_diffuse.begin()); std::copy(draw.material_ambient, draw.material_ambient + 4, state.material_ambient.begin()); std::copy(draw.material_emissive, draw.material_emissive + 4, state.material_emissive.begin()); state.global_ambient = unpack_argb(draw.ambient); const float* v = draw.view; for (int i = 0; i < 8; ++i) { const auto& light = draw.lights[i]; const float* p = light.position; const float* d = light.direction; std::array position{}, direction{}; for (int c = 0; c < 3; ++c) { position[c] = p[0] * v[c] + p[1] * v[4 + c] + p[2] * v[8 + c] + v[12 + c]; direction[c] = d[0] * v[c] + d[1] * v[4 + c] + d[2] * v[8 + c]; } const float length = std::sqrt(direction[0] * direction[0] + direction[1] * direction[1] + direction[2] * direction[2]); if (length > 0.0f) for (float& c : direction) c /= length; state.light_position_type[i] = {position[0], position[1], position[2], float(light.type)}; state.light_direction_range[i] = {direction[0], direction[1], direction[2], light.range}; std::copy(light.diffuse, light.diffuse + 4, state.light_diffuse[i].begin()); std::copy(light.ambient, light.ambient + 4, state.light_ambient[i].begin()); state.light_attenuation[i] = {light.attenuation[0], light.attenuation[1], light.attenuation[2], light.falloff}; state.light_spot[i] = {std::cos(light.theta * 0.5f), std::cos(light.phi * 0.5f), 0, 0}; } return state; } std::uint64_t hash_bytes(std::uint64_t hash, const void* bytes, std::size_t size) { const auto* data = static_cast(bytes); std::size_t i = 0; for (; i + sizeof(std::uint64_t) <= size; i += sizeof(std::uint64_t)) { std::uint64_t word = 0; std::memcpy(&word, data + i, sizeof(word)); hash = (hash ^ word) * 1099511628211ull; } for (; i < size; ++i) hash = (hash ^ data[i]) * 1099511628211ull; hash = (hash ^ size) * 1099511628211ull; return hash; } // Immediate-mode draws have no source-buffer key. For them, verify content before reusing a slot. std::uint64_t geometry_hash(const Render3DDraw& draw) { std::uint64_t hash = 14695981039346656037ull; const std::uint32_t flags = (draw.lines ? 1u : 0u) | (draw.pretransformed ? 2u : 0u); hash = hash_bytes(hash, &flags, sizeof(flags)); hash = hash_bytes(hash, draw.positions.data(), draw.positions.size() * sizeof(float)); hash = hash_bytes(hash, draw.rhw.data(), draw.rhw.size() * sizeof(float)); hash = hash_bytes(hash, draw.vertex_fog.data(), draw.vertex_fog.size() * sizeof(float)); hash = hash_bytes(hash, draw.normals.data(), draw.normals.size() * sizeof(float)); hash = hash_bytes(hash, draw.uv0.data(), draw.uv0.size() * sizeof(float)); hash = hash_bytes(hash, draw.uv1.data(), draw.uv1.size() * sizeof(float)); hash = hash_bytes(hash, draw.diffuse.data(), draw.diffuse.size() * sizeof(std::uint32_t)); return hash_bytes(hash, draw.indices.data(), draw.indices.size() * sizeof(std::uint32_t)); } } // namespace mt_host