//----------------------------------------------------------------------------// // // // ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation // // and distributed under the MIT License (MIT). // // // // Copyright (c) Guillaume Blanc // // // // Permission is hereby granted, free of charge, to any person obtaining a // // copy of this software and associated documentation files (the "Software"), // // to deal in the Software without restriction, including without limitation // // the rights to use, copy, modify, merge, publish, distribute, sublicense, // // and/or sell copies of the Software, and to permit persons to whom the // // Software is furnished to do so, subject to the following conditions: // // // // The above copyright notice and this permission notice shall be included in // // all copies or substantial portions of the Software. // // // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // // DEALINGS IN THE SOFTWARE. // // // //----------------------------------------------------------------------------// #define OZZ_INCLUDE_PRIVATE_HEADER // Allows to include private headers. #include "renderer_impl.h" #include "camera.h" #include "framework/mesh.h" #include "icosphere.h" #include "immediate.h" #include "ozz/animation/runtime/local_to_model_job.h" #include "ozz/animation/runtime/skeleton.h" #include "ozz/animation/runtime/skeleton_utils.h" #include "ozz/base/log.h" #include "ozz/base/maths/box.h" #include "ozz/base/maths/math_ex.h" #include "ozz/base/maths/simd_math.h" #include "ozz/base/maths/vec_float.h" #include "ozz/base/memory/allocator.h" #include "ozz/base/platform.h" #include "ozz/geometry/runtime/skinning_job.h" #include "shader.h" namespace ozz { namespace sample { namespace internal { namespace { // A vertex made of positions and normals. struct VertexPNC { math::Float3 pos; math::Float3 normal; Color color; }; } // namespace RendererImpl::Model::Model() : vbo(0), mode(GL_POINTS), count(0) {} RendererImpl::Model::~Model() { if (vbo) { GL(DeleteBuffers(1, &vbo)); vbo = 0; } } RendererImpl::RendererImpl(Camera* _camera) : camera_(_camera), dynamic_array_bo_(0), dynamic_index_bo_(0), checkered_texture_(0) {} RendererImpl::~RendererImpl() { if (dynamic_array_bo_) { GL(DeleteBuffers(1, &dynamic_array_bo_)); dynamic_array_bo_ = 0; } if (dynamic_index_bo_) { GL(DeleteBuffers(1, &dynamic_index_bo_)); dynamic_index_bo_ = 0; } if (checkered_texture_) { GL(DeleteTextures(1, &checkered_texture_)); checkered_texture_ = 0; } } bool RendererImpl::Initialize() { if (!InitOpenGLExtensions()) { return false; } if (!InitPostureRendering()) { return false; } if (!InitCheckeredTexture()) { return false; } // Builds the dynamic vbo GL(GenBuffers(1, &dynamic_array_bo_)); GL(GenBuffers(1, &dynamic_index_bo_)); // Allocate immediate mode renderer; immediate_ = make_unique(this); if (!immediate_->Initialize()) { return false; } // Instantiate ambient rendering shader. ambient_shader = AmbientShader::Build(); if (!ambient_shader) { return false; } // Instantiate ambient textured rendering shader. ambient_textured_shader = AmbientTexturedShader::Build(); if (!ambient_textured_shader) { return false; } // Instantiate instanced ambient rendering shader. if (GL_ARB_instanced_arrays_supported) { ambient_shader_instanced = AmbientShaderInstanced::Build(); if (!ambient_shader_instanced) { return false; } } return true; } bool RendererImpl::DrawAxes(const ozz::math::Float4x4& _transform) { GlImmediatePC im(immediate_renderer(), GL_LINES, _transform); GlImmediatePC::Vertex v = {{0.f, 0.f, 0.f}, {0, 0, 0, 0xff}}; // X axis (green). v.pos[0] = 0.f; v.pos[1] = 0.f; v.pos[2] = 0.f; v.rgba[0] = 0xff; v.rgba[1] = 0; v.rgba[2] = 0; im.PushVertex(v); v.pos[0] = 1.f; im.PushVertex(v); // Y axis (green). v.pos[0] = 0.f; v.pos[1] = 0.f; v.pos[2] = 0.f; v.rgba[0] = 0; v.rgba[1] = 0xff; v.rgba[2] = 0; im.PushVertex(v); v.pos[1] = 1.f; im.PushVertex(v); // Z axis (green). v.pos[0] = 0.f; v.pos[1] = 0.f; v.pos[2] = 0.f; v.rgba[0] = 0; v.rgba[1] = 0; v.rgba[2] = 0xff; im.PushVertex(v); v.pos[2] = 1.f; im.PushVertex(v); return true; } bool RendererImpl::DrawGrid(int _cell_count, float _cell_size) { const float extent = _cell_count * _cell_size; const float half_extent = extent * 0.5f; const ozz::math::Float3 corner(-half_extent, 0, -half_extent); GL(DepthMask(GL_FALSE)); GL(Enable(GL_BLEND)); GL(BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); GL(Disable(GL_CULL_FACE)); { GlImmediatePC im(immediate_renderer(), GL_TRIANGLE_STRIP, ozz::math::Float4x4::identity()); GlImmediatePC::Vertex v = {{0.f, 0.f, 0.f}, {0x80, 0xc0, 0xd0, 0xb0}}; v.pos[0] = corner.x; v.pos[1] = corner.y; v.pos[2] = corner.z; im.PushVertex(v); v.pos[2] = corner.z + extent; im.PushVertex(v); v.pos[0] = corner.x + extent; v.pos[2] = corner.z; im.PushVertex(v); v.pos[2] = corner.z + extent; im.PushVertex(v); } GL(Disable(GL_BLEND)); GL(Enable(GL_CULL_FACE)); { GlImmediatePC im(immediate_renderer(), GL_LINES, ozz::math::Float4x4::identity()); // Renders lines along X axis. GlImmediatePC::Vertex begin = {{corner.x, corner.y, corner.z}, {0x54, 0x55, 0x50, 0xff}}; GlImmediatePC::Vertex end = begin; end.pos[0] += extent; for (int i = 0; i < _cell_count + 1; ++i) { im.PushVertex(begin); im.PushVertex(end); begin.pos[2] += _cell_size; end.pos[2] += _cell_size; } // Renders lines along Z axis. begin.pos[0] = corner.x; begin.pos[1] = corner.y; begin.pos[2] = corner.z; end = begin; end.pos[2] += extent; for (int i = 0; i < _cell_count + 1; ++i) { im.PushVertex(begin); im.PushVertex(end); begin.pos[0] += _cell_size; end.pos[0] += _cell_size; } } GL(DepthMask(GL_TRUE)); return true; } // Computes the model space bind pose and renders it. bool RendererImpl::DrawSkeleton(const ozz::animation::Skeleton& _skeleton, const ozz::math::Float4x4& _transform, bool _draw_joints) { using ozz::math::Float4x4; const int num_joints = _skeleton.num_joints(); if (!num_joints) { return true; } // Reallocate matrix array if necessary. prealloc_models_.resize(num_joints); // Compute model space bind pose. ozz::animation::LocalToModelJob job; job.input = _skeleton.joint_bind_poses(); job.output = make_span(prealloc_models_); job.skeleton = &_skeleton; if (!job.Run()) { return false; } // Forwards to rendering. return DrawPosture(_skeleton, job.output, _transform, _draw_joints); } bool RendererImpl::InitPostureRendering() { const float kInter = .2f; { // Prepares bone mesh. const math::Float3 pos[6] = { math::Float3(1.f, 0.f, 0.f), math::Float3(kInter, .1f, .1f), math::Float3(kInter, .1f, -.1f), math::Float3(kInter, -.1f, -.1f), math::Float3(kInter, -.1f, .1f), math::Float3(0.f, 0.f, 0.f)}; const math::Float3 normals[8] = { Normalize(Cross(pos[2] - pos[1], pos[2] - pos[0])), Normalize(Cross(pos[1] - pos[2], pos[1] - pos[5])), Normalize(Cross(pos[3] - pos[2], pos[3] - pos[0])), Normalize(Cross(pos[2] - pos[3], pos[2] - pos[5])), Normalize(Cross(pos[4] - pos[3], pos[4] - pos[0])), Normalize(Cross(pos[3] - pos[4], pos[3] - pos[5])), Normalize(Cross(pos[1] - pos[4], pos[1] - pos[0])), Normalize(Cross(pos[4] - pos[1], pos[4] - pos[5]))}; const Color white = {0xff, 0xff, 0xff, 0xff}; const VertexPNC bones[24] = { {pos[0], normals[0], white}, {pos[2], normals[0], white}, {pos[1], normals[0], white}, {pos[5], normals[1], white}, {pos[1], normals[1], white}, {pos[2], normals[1], white}, {pos[0], normals[2], white}, {pos[3], normals[2], white}, {pos[2], normals[2], white}, {pos[5], normals[3], white}, {pos[2], normals[3], white}, {pos[3], normals[3], white}, {pos[0], normals[4], white}, {pos[4], normals[4], white}, {pos[3], normals[4], white}, {pos[5], normals[5], white}, {pos[3], normals[5], white}, {pos[4], normals[5], white}, {pos[0], normals[6], white}, {pos[1], normals[6], white}, {pos[4], normals[6], white}, {pos[5], normals[7], white}, {pos[4], normals[7], white}, {pos[1], normals[7], white}}; // Builds and fills the vbo. Model& bone = models_[0]; bone.mode = GL_TRIANGLES; bone.count = OZZ_ARRAY_SIZE(bones); GL(GenBuffers(1, &bone.vbo)); GL(BindBuffer(GL_ARRAY_BUFFER, bone.vbo)); GL(BufferData(GL_ARRAY_BUFFER, sizeof(bones), bones, GL_STATIC_DRAW)); GL(BindBuffer(GL_ARRAY_BUFFER, 0)); // Unbinds. // Init bone shader. bone.shader = BoneShader::Build(); if (!bone.shader) { return false; } } { // Prepares joint mesh. const int kNumSlices = 20; const int kNumPointsPerCircle = kNumSlices + 1; const int kNumPointsYZ = kNumPointsPerCircle; const int kNumPointsXY = kNumPointsPerCircle + kNumPointsPerCircle / 4; const int kNumPointsXZ = kNumPointsPerCircle; const int kNumPoints = kNumPointsXY + kNumPointsXZ + kNumPointsYZ; const float kRadius = kInter; // Radius multiplier. const Color red = {0xff, 0xc0, 0xc0, 0xff}; const Color green = {0xc0, 0xff, 0xc0, 0xff}; const Color blue = {0xc0, 0xc0, 0xff, 0xff}; VertexPNC joints[kNumPoints]; // Fills vertices. int index = 0; for (int j = 0; j < kNumPointsYZ; ++j) { // YZ plan. float angle = j * math::k2Pi / kNumSlices; float s = sinf(angle), c = cosf(angle); VertexPNC& vertex = joints[index++]; vertex.pos = math::Float3(0.f, c * kRadius, s * kRadius); vertex.normal = math::Float3(0.f, c, s); vertex.color = red; } for (int j = 0; j < kNumPointsXY; ++j) { // XY plan. float angle = j * math::k2Pi / kNumSlices; float s = sinf(angle), c = cosf(angle); VertexPNC& vertex = joints[index++]; vertex.pos = math::Float3(s * kRadius, c * kRadius, 0.f); vertex.normal = math::Float3(s, c, 0.f); vertex.color = blue; } for (int j = 0; j < kNumPointsXZ; ++j) { // XZ plan. float angle = j * math::k2Pi / kNumSlices; float s = sinf(angle), c = cosf(angle); VertexPNC& vertex = joints[index++]; vertex.pos = math::Float3(c * kRadius, 0.f, -s * kRadius); vertex.normal = math::Float3(c, 0.f, -s); vertex.color = green; } assert(index == kNumPoints); // Builds and fills the vbo. Model& joint = models_[1]; joint.mode = GL_LINE_STRIP; joint.count = OZZ_ARRAY_SIZE(joints); GL(GenBuffers(1, &joint.vbo)); GL(BindBuffer(GL_ARRAY_BUFFER, joint.vbo)); GL(BufferData(GL_ARRAY_BUFFER, sizeof(joints), joints, GL_STATIC_DRAW)); GL(BindBuffer(GL_ARRAY_BUFFER, 0)); // Unbinds. // Init joint shader. joint.shader = JointShader::Build(); if (!joint.shader) { return false; } } return true; } bool RendererImpl::InitCheckeredTexture() { const int kWidth = 1024; const int kCases = 64; GL(GenTextures(1, &checkered_texture_)); GL(BindTexture(GL_TEXTURE_2D, checkered_texture_)); GL(TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)); GL(TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)); GL(TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GL(TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); GL(PixelStorei(GL_UNPACK_ALIGNMENT, 1)); // Allocates for biggest mip level. const size_t buffer_size = 3 * kWidth * kWidth; uint8_t* pixels = reinterpret_cast( memory::default_allocator()->Allocate(buffer_size, 16)); // Create the checkered pattern on all mip levels. int level_width = kWidth; for (int level = 0; level_width > 0; ++level, level_width /= 2) { if (level_width >= kCases) { const int case_width = level_width / kCases; for (int j = 0; j < level_width; ++j) { const int cpntj = (j / case_width) & 1; for (int i = 0; i < kCases; ++i) { const int cpnti = i & 1; const bool white_case = (cpnti ^ cpntj) != 0; const uint8_t cpntr = white_case ? 0xff : j * 255 / level_width & 0xff; const uint8_t cpntg = white_case ? 0xff : i * 255 / kCases & 0xff; const uint8_t cpntb = white_case ? 0xff : 0; const int case_start = j * level_width + i * case_width; for (int k = case_start; k < case_start + case_width; ++k) { pixels[k * 3 + 0] = cpntr; pixels[k * 3 + 1] = cpntg; pixels[k * 3 + 2] = cpntb; } } } } else { // Mimaps where width is smaller than the number of cases. for (int j = 0; j < level_width; ++j) { for (int i = 0; i < level_width; ++i) { pixels[(j * level_width + i) * 3 + 0] = 0x7f; pixels[(j * level_width + i) * 3 + 1] = 0x7f; pixels[(j * level_width + i) * 3 + 2] = 0x7f; } } } GL(TexImage2D(GL_TEXTURE_2D, level, GL_RGB, level_width, level_width, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels)); } GL(BindTexture(GL_TEXTURE_2D, 0)); memory::default_allocator()->Deallocate(pixels); return true; } namespace { int DrawPosture_FillUniforms(const ozz::animation::Skeleton& _skeleton, ozz::span _matrices, float* _uniforms, int _max_instances) { assert(IsAligned(_uniforms, alignof(math::SimdFloat4))); // Prepares computation constants. const int num_joints = _skeleton.num_joints(); const span& parents = _skeleton.joint_parents(); int instances = 0; for (int i = 0; i < num_joints && instances < _max_instances; ++i) { // Root isn't rendered. const int16_t parent_id = parents[i]; if (parent_id == ozz::animation::Skeleton::kNoParent) { continue; } // Selects joint matrices. const math::Float4x4& parent = _matrices[parent_id]; const math::Float4x4& current = _matrices[i]; // Copy parent joint's raw matrix, to render a bone between the parent // and current matrix. float* uniform = _uniforms + instances * 16; math::StorePtr(parent.cols[0], uniform + 0); math::StorePtr(parent.cols[1], uniform + 4); math::StorePtr(parent.cols[2], uniform + 8); math::StorePtr(parent.cols[3], uniform + 12); // Set bone direction (bone_dir). The shader expects to find it at index // [3,7,11] of the matrix. // Index 15 is used to store whether a bone should be rendered, // otherwise it's a leaf. float bone_dir[4]; math::StorePtrU(current.cols[3] - parent.cols[3], bone_dir); uniform[3] = bone_dir[0]; uniform[7] = bone_dir[1]; uniform[11] = bone_dir[2]; uniform[15] = 1.f; // Enables bone rendering. // Next instance. ++instances; uniform += 16; // Only the joint is rendered for leaves, the bone model isn't. if (IsLeaf(_skeleton, i)) { // Copy current joint's raw matrix. uniform = _uniforms + instances * 16; math::StorePtr(current.cols[0], uniform + 0); math::StorePtr(current.cols[1], uniform + 4); math::StorePtr(current.cols[2], uniform + 8); math::StorePtr(current.cols[3], uniform + 12); // Re-use bone_dir to fix the size of the leaf (same as previous bone). // The shader expects to find it at index [3,7,11] of the matrix. uniform[3] = bone_dir[0]; uniform[7] = bone_dir[1]; uniform[11] = bone_dir[2]; uniform[15] = 0.f; // Disables bone rendering. ++instances; } } return instances; } } // namespace // Draw posture internal non-instanced rendering fall back implementation. void RendererImpl::DrawPosture_Impl(const ozz::math::Float4x4& _transform, const float* _uniforms, int _instance_count, bool _draw_joints) { // Loops through models and instances. for (int i = 0; i < (_draw_joints ? 2 : 1); ++i) { const Model& model = models_[i]; // Setup model vertex data. GL(BindBuffer(GL_ARRAY_BUFFER, model.vbo)); // Bind shader model.shader->Bind(_transform, camera_->view_proj(), sizeof(VertexPNC), 0, sizeof(VertexPNC), 12, sizeof(VertexPNC), 24); GL(BindBuffer(GL_ARRAY_BUFFER, 0)); // Draw loop. const GLint joint_uniform = model.shader->joint_uniform(); for (int j = 0; j < _instance_count; ++j) { GL(UniformMatrix4fv(joint_uniform, 1, false, _uniforms + 16 * j)); GL(DrawArrays(model.mode, 0, model.count)); } model.shader->Unbind(); } } // "Draw posture" internal instanced rendering implementation. void RendererImpl::DrawPosture_InstancedImpl( const ozz::math::Float4x4& _transform, const float* _uniforms, int _instance_count, bool _draw_joints) { // Maps the dynamic buffer and update it. GL(BindBuffer(GL_ARRAY_BUFFER, dynamic_array_bo_)); const size_t vbo_size = _instance_count * 16 * sizeof(float); GL(BufferData(GL_ARRAY_BUFFER, vbo_size, _uniforms, GL_STREAM_DRAW)); GL(BindBuffer(GL_ARRAY_BUFFER, 0)); // Renders models. for (int i = 0; i < (_draw_joints ? 2 : 1); ++i) { const Model& model = models_[i]; // Setup model vertex data. GL(BindBuffer(GL_ARRAY_BUFFER, model.vbo)); // Bind shader model.shader->Bind(_transform, camera_->view_proj(), sizeof(VertexPNC), 0, sizeof(VertexPNC), 12, sizeof(VertexPNC), 24); GL(BindBuffer(GL_ARRAY_BUFFER, 0)); // Setup instanced GL context. const GLint joint_attrib = model.shader->joint_instanced_attrib(); GL(BindBuffer(GL_ARRAY_BUFFER, dynamic_array_bo_)); GL(EnableVertexAttribArray(joint_attrib + 0)); GL(EnableVertexAttribArray(joint_attrib + 1)); GL(EnableVertexAttribArray(joint_attrib + 2)); GL(EnableVertexAttribArray(joint_attrib + 3)); GL(VertexAttribDivisor_(joint_attrib + 0, 1)); GL(VertexAttribDivisor_(joint_attrib + 1, 1)); GL(VertexAttribDivisor_(joint_attrib + 2, 1)); GL(VertexAttribDivisor_(joint_attrib + 3, 1)); GL(VertexAttribPointer(joint_attrib + 0, 4, GL_FLOAT, GL_FALSE, sizeof(math::Float4x4), GL_PTR_OFFSET(0))); GL(VertexAttribPointer(joint_attrib + 1, 4, GL_FLOAT, GL_FALSE, sizeof(math::Float4x4), GL_PTR_OFFSET(16))); GL(VertexAttribPointer(joint_attrib + 2, 4, GL_FLOAT, GL_FALSE, sizeof(math::Float4x4), GL_PTR_OFFSET(32))); GL(VertexAttribPointer(joint_attrib + 3, 4, GL_FLOAT, GL_FALSE, sizeof(math::Float4x4), GL_PTR_OFFSET(48))); GL(BindBuffer(GL_ARRAY_BUFFER, 0)); GL(DrawArraysInstanced_(model.mode, 0, model.count, _instance_count)); GL(DisableVertexAttribArray(joint_attrib + 0)); GL(DisableVertexAttribArray(joint_attrib + 1)); GL(DisableVertexAttribArray(joint_attrib + 2)); GL(DisableVertexAttribArray(joint_attrib + 3)); GL(VertexAttribDivisor_(joint_attrib + 0, 0)); GL(VertexAttribDivisor_(joint_attrib + 1, 0)); GL(VertexAttribDivisor_(joint_attrib + 2, 0)); GL(VertexAttribDivisor_(joint_attrib + 3, 0)); model.shader->Unbind(); } } // Uses GL_ARB_instanced_arrays_supported as a first choice to render the whole // skeleton in a single draw call. Does a draw call per joint if no extension // can help. bool RendererImpl::DrawPosture(const ozz::animation::Skeleton& _skeleton, ozz::span _matrices, const ozz::math::Float4x4& _transform, bool _draw_joints) { if (_matrices.size() < static_cast(_skeleton.num_joints())) { return false; } // Convert matrices to uniforms. const int max_skeleton_pieces = animation::Skeleton::kMaxJoints * 2; const size_t max_uniforms_size = max_skeleton_pieces * 2 * 16 * sizeof(float); float* uniforms = static_cast(scratch_buffer_.Resize(max_uniforms_size)); const int instance_count = DrawPosture_FillUniforms( _skeleton, _matrices, uniforms, max_skeleton_pieces); assert(instance_count <= max_skeleton_pieces); if (GL_ARB_instanced_arrays_supported) { DrawPosture_InstancedImpl(_transform, uniforms, instance_count, _draw_joints); } else { DrawPosture_Impl(_transform, uniforms, instance_count, _draw_joints); } return true; } bool RendererImpl::DrawBoxIm(const ozz::math::Box& _box, const ozz::math::Float4x4& _transform, const Color _colors[2]) { { // Filled boxed GlImmediatePC im(immediate_renderer(), GL_TRIANGLE_STRIP, _transform); GlImmediatePC::Vertex v = { {0, 0, 0}, {_colors[0].r, _colors[0].g, _colors[0].b, _colors[0].a}}; // First 3 cube faces v.pos[0] = _box.max.x; v.pos[1] = _box.min.y; v.pos[2] = _box.min.z; im.PushVertex(v); v.pos[0] = _box.min.x; im.PushVertex(v); v.pos[0] = _box.max.x; v.pos[1] = _box.max.y; im.PushVertex(v); v.pos[0] = _box.min.x; im.PushVertex(v); v.pos[0] = _box.max.x; v.pos[2] = _box.max.z; im.PushVertex(v); v.pos[0] = _box.min.x; im.PushVertex(v); v.pos[0] = _box.max.x; v.pos[1] = _box.min.y; im.PushVertex(v); v.pos[0] = _box.min.x; im.PushVertex(v); // Link next 3 cube faces with degenerated triangles. im.PushVertex(v); v.pos[0] = _box.min.x; v.pos[1] = _box.max.y; im.PushVertex(v); im.PushVertex(v); // Last 3 cube faces. v.pos[2] = _box.min.z; im.PushVertex(v); v.pos[1] = _box.min.y; v.pos[2] = _box.max.z; im.PushVertex(v); v.pos[2] = _box.min.z; im.PushVertex(v); v.pos[0] = _box.max.x; v.pos[2] = _box.max.z; im.PushVertex(v); v.pos[2] = _box.min.z; im.PushVertex(v); v.pos[1] = _box.max.y; v.pos[2] = _box.max.z; im.PushVertex(v); v.pos[2] = _box.min.z; im.PushVertex(v); } { // Wireframe boxed GlImmediatePC im(immediate_renderer(), GL_LINES, _transform); GlImmediatePC::Vertex v = { {0, 0, 0}, {_colors[1].r, _colors[1].g, _colors[1].b, _colors[1].a}}; // First face. v.pos[0] = _box.min.x; v.pos[1] = _box.min.y; v.pos[2] = _box.min.z; im.PushVertex(v); v.pos[1] = _box.max.y; im.PushVertex(v); im.PushVertex(v); v.pos[0] = _box.max.x; im.PushVertex(v); im.PushVertex(v); v.pos[1] = _box.min.y; im.PushVertex(v); im.PushVertex(v); v.pos[0] = _box.min.x; im.PushVertex(v); // Second face. v.pos[2] = _box.max.z; im.PushVertex(v); v.pos[1] = _box.max.y; im.PushVertex(v); im.PushVertex(v); v.pos[0] = _box.max.x; im.PushVertex(v); im.PushVertex(v); v.pos[1] = _box.min.y; im.PushVertex(v); im.PushVertex(v); v.pos[0] = _box.min.x; im.PushVertex(v); // Link faces. im.PushVertex(v); v.pos[2] = _box.min.z; im.PushVertex(v); v.pos[1] = _box.max.y; im.PushVertex(v); v.pos[2] = _box.max.z; im.PushVertex(v); v.pos[0] = _box.max.x; im.PushVertex(v); v.pos[2] = _box.min.z; im.PushVertex(v); v.pos[1] = _box.min.y; im.PushVertex(v); v.pos[2] = _box.max.z; im.PushVertex(v); } return true; } bool RendererImpl::DrawBoxShaded( const ozz::math::Box& _box, ozz::span _transforms, Color _color) { // Early out if no instance to render. if (_transforms.size() == 0) { return true; } const math::Float3 pos[8] = { math::Float3(_box.min.x, _box.min.y, _box.min.z), math::Float3(_box.max.x, _box.min.y, _box.min.z), math::Float3(_box.max.x, _box.max.y, _box.min.z), math::Float3(_box.min.x, _box.max.y, _box.min.z), math::Float3(_box.min.x, _box.min.y, _box.max.z), math::Float3(_box.max.x, _box.min.y, _box.max.z), math::Float3(_box.max.x, _box.max.y, _box.max.z), math::Float3(_box.min.x, _box.max.y, _box.max.z)}; const math::Float3 normals[6] = { math::Float3(-1, 0, 0), math::Float3(1, 0, 0), math::Float3(0, -1, 0), math::Float3(0, 1, 0), math::Float3(0, 0, -1), math::Float3(0, 0, 1)}; const VertexPNC vertices[36] = { {pos[0], normals[4], _color}, {pos[3], normals[4], _color}, {pos[1], normals[4], _color}, {pos[3], normals[4], _color}, {pos[2], normals[4], _color}, {pos[1], normals[4], _color}, {pos[2], normals[3], _color}, {pos[3], normals[3], _color}, {pos[7], normals[3], _color}, {pos[7], normals[3], _color}, {pos[6], normals[3], _color}, {pos[2], normals[3], _color}, {pos[5], normals[5], _color}, {pos[6], normals[5], _color}, {pos[7], normals[5], _color}, {pos[5], normals[5], _color}, {pos[7], normals[5], _color}, {pos[4], normals[5], _color}, {pos[0], normals[2], _color}, {pos[1], normals[2], _color}, {pos[4], normals[2], _color}, {pos[4], normals[2], _color}, {pos[1], normals[2], _color}, {pos[5], normals[2], _color}, {pos[0], normals[0], _color}, {pos[4], normals[0], _color}, {pos[3], normals[0], _color}, {pos[4], normals[0], _color}, {pos[7], normals[0], _color}, {pos[3], normals[0], _color}, {pos[5], normals[1], _color}, {pos[1], normals[1], _color}, {pos[2], normals[1], _color}, {pos[5], normals[1], _color}, {pos[2], normals[1], _color}, {pos[6], normals[1], _color}}; const GLsizei stride = sizeof(VertexPNC); const GLsizei positions_offset = 0; const GLsizei normals_offset = positions_offset + sizeof(float) * 3; const GLsizei colors_offset = normals_offset + sizeof(float) * 3; if (GL_ARB_instanced_arrays_supported) { // Buffer object will contain vertices and model matrices. const size_t bo_size = sizeof(vertices) + _transforms.size_bytes(); GL(BindBuffer(GL_ARRAY_BUFFER, dynamic_array_bo_)); GL(BufferData(GL_ARRAY_BUFFER, bo_size, nullptr, GL_STREAM_DRAW)); // Pushes vertices GL(BufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices)); // Pushes matrices const size_t models_offset = sizeof(vertices); GL(BufferSubData(GL_ARRAY_BUFFER, models_offset, _transforms.size_bytes(), _transforms.data())); ambient_shader_instanced->Bind(models_offset, camera()->view_proj(), stride, positions_offset, stride, normals_offset, stride, colors_offset); GL(BindBuffer(GL_ARRAY_BUFFER, 0)); GL(DrawArraysInstanced_(GL_TRIANGLES, 0, OZZ_ARRAY_SIZE(vertices), static_cast(_transforms.size()))); // Unbinds. ambient_shader_instanced->Unbind(); } else { // Reallocate vertex buffer. GL(BindBuffer(GL_ARRAY_BUFFER, dynamic_array_bo_)); GL(BufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STREAM_DRAW)); for (size_t i = 0; i < _transforms.size(); i++) { const ozz::math::Float4x4& transform = _transforms[i]; ambient_shader->Bind(transform, camera()->view_proj(), stride, positions_offset, stride, normals_offset, stride, colors_offset); // Draws the mesh. GL(DrawArrays(GL_TRIANGLES, 0, OZZ_ARRAY_SIZE(vertices))); // Unbinds. ambient_shader->Unbind(); } GL(BindBuffer(GL_ARRAY_BUFFER, 0)); } return true; } // Renders a sphere at a specified location. bool RendererImpl::DrawSphereIm(float _radius, const ozz::math::Float4x4& _transform, const Color _color) { { // Filled boxed const ozz::math::Float4x4& transform = Scale(_transform, ozz::math::simd_float4::Load(_radius, _radius, _radius, 1.f)); GlImmediatePC im(immediate_renderer(), GL_TRIANGLES, transform); GlImmediatePC::Vertex v = {{0, 0, 0}, {_color.r, _color.g, _color.b, _color.a}}; for (int i = 0; i < icosphere::kNumIndices; ++i) { const uint16_t vi = icosphere::kIndices[i]; v.pos[0] = icosphere::kVertices[vi * 3 + 0]; v.pos[1] = icosphere::kVertices[vi * 3 + 1]; v.pos[2] = icosphere::kVertices[vi * 3 + 2]; im.PushVertex(v); } } return true; } // Renders shaded spheres at specified locations. bool RendererImpl::DrawSphereShaded( float _radius, ozz::span _transforms, Color _color) { // Early out if no instance to render. if (_transforms.size() == 0) { return true; } ozz::math::SimdFloat4 radius = ozz::math::simd_float4::Load(_radius, _radius, _radius, 1.f); // Setup indices GL(BindBuffer(GL_ELEMENT_ARRAY_BUFFER, dynamic_index_bo_)); GL(BufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(icosphere::kIndices), icosphere::kIndices, GL_STREAM_DRAW)); // Vertices const GLsizei positions_offset = 0; const GLsizei positions_stride = sizeof(float) * 3; const GLsizei normals_offset = positions_offset; // Normals and positions are the same. const GLsizei normals_stride = positions_stride; const GLsizei colors_offset = sizeof(icosphere::kVertices); if (GL_ARB_instanced_arrays_supported) { const GLsizei colors_stride = 0; const GLsizei colors_size = sizeof(uint8_t) * 4; const GLsizei models_offset = sizeof(icosphere::kVertices) + colors_size; const GLsizei bo_size = models_offset + static_cast(_transforms.size_bytes()); GL(BindBuffer(GL_ARRAY_BUFFER, dynamic_array_bo_)); GL(BufferData(GL_ARRAY_BUFFER, bo_size, nullptr, GL_STREAM_DRAW)); GL(BufferSubData(GL_ARRAY_BUFFER, positions_offset, sizeof(icosphere::kVertices), icosphere::kVertices)); GL(BufferSubData(GL_ARRAY_BUFFER, colors_offset, colors_size, &_color)); ozz::math::Float4x4* models = static_cast( scratch_buffer_.Resize(_transforms.size_bytes())); for (size_t i = 0; i < _transforms.size(); ++i) { models[i] = Scale(_transforms[i], radius); } GL(BufferSubData(GL_ARRAY_BUFFER, models_offset, _transforms.size_bytes(), models)); ambient_shader_instanced->Bind(models_offset, camera()->view_proj(), positions_stride, positions_offset, normals_stride, normals_offset, colors_stride, colors_offset); static_assert(sizeof(icosphere::kIndices[0]) == 2, "Indices must be 2 bytes"); GL(DrawElementsInstanced_(GL_TRIANGLES, OZZ_ARRAY_SIZE(icosphere::kIndices), GL_UNSIGNED_SHORT, 0, static_cast(_transforms.size()))); // Unbinds. ambient_shader_instanced->Unbind(); } else { // OpenGL doesn't support 0 stride (without glVertexAttribDivisor // extension), so we must copy a color for each vertex. const GLsizei colors_stride = sizeof(uint8_t) * 4; const GLsizei colors_size = colors_stride * icosphere::kNumVertices; const GLsizei bo_size = sizeof(icosphere::kVertices) + colors_size; // Reallocate vertex buffer. GL(BindBuffer(GL_ARRAY_BUFFER, dynamic_array_bo_)); GL(BufferData(GL_ARRAY_BUFFER, bo_size, nullptr, GL_STREAM_DRAW)); GL(BufferSubData(GL_ARRAY_BUFFER, positions_offset, sizeof(icosphere::kVertices), icosphere::kVertices)); Color* colors = static_cast(scratch_buffer_.Resize(colors_size)); for (int i = 0; i < icosphere::kNumVertices; ++i) { colors[i] = _color; } GL(BufferSubData(GL_ARRAY_BUFFER, colors_offset, colors_size, colors)); for (size_t i = 0; i < _transforms.size(); i++) { const ozz::math::Float4x4& transform = Scale(_transforms[i], radius); ambient_shader->Bind(transform, camera()->view_proj(), positions_stride, positions_offset, normals_stride, normals_offset, colors_stride, colors_offset); static_assert(sizeof(icosphere::kIndices[0]) == 2, "Indices must be 2 bytes"); GL(DrawElements(GL_TRIANGLES, OZZ_ARRAY_SIZE(icosphere::kIndices), GL_UNSIGNED_SHORT, 0)); // Unbinds. ambient_shader->Unbind(); } GL(BindBuffer(GL_ARRAY_BUFFER, 0)); GL(BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); } return true; } bool RendererImpl::DrawSegment(const math::Float3& _begin, const math::Float3& _end, Color _color, const ozz::math::Float4x4& _transform) { const math::Float3 dir(_end - _begin); return DrawVectors(ozz::span(&_begin.x, 3), 12, ozz::span(&dir.x, 3), 12, 1, 1.f, _color, _transform); } bool RendererImpl::DrawVectors(ozz::span _positions, size_t _positions_stride, ozz::span _directions, size_t _directions_stride, int _num_vectors, float _vector_length, Color _color, const ozz::math::Float4x4& _transform) { // Invalid range length. if (PointerStride(_positions.begin(), _positions_stride * _num_vectors) > _positions.end() || PointerStride(_directions.begin(), _directions_stride * _num_vectors) > _directions.end()) { return false; } GlImmediatePC im(immediate_renderer(), GL_LINES, _transform); GlImmediatePC::Vertex v = {{0, 0, 0}, {_color.r, _color.g, _color.b, _color.a}}; for (int i = 0; i < _num_vectors; ++i) { const float* position = PointerStride(_positions.data(), _positions_stride * i); v.pos[0] = position[0]; v.pos[1] = position[1]; v.pos[2] = position[2]; im.PushVertex(v); const float* direction = PointerStride(_directions.data(), _directions_stride * i); v.pos[0] = position[0] + direction[0] * _vector_length; v.pos[1] = position[1] + direction[1] * _vector_length; v.pos[2] = position[2] + direction[2] * _vector_length; im.PushVertex(v); } return true; } bool RendererImpl::DrawBinormals( ozz::span _positions, size_t _positions_stride, ozz::span _normals, size_t _normals_stride, ozz::span _tangents, size_t _tangents_stride, ozz::span _handenesses, size_t _handenesses_stride, int _num_vectors, float _vector_length, Color _color, const ozz::math::Float4x4& _transform) { // Invalid range length. if (PointerStride(_positions.begin(), _positions_stride * _num_vectors) > _positions.end() || PointerStride(_normals.begin(), _normals_stride * _num_vectors) > _normals.end() || PointerStride(_tangents.begin(), _tangents_stride * _num_vectors) > _tangents.end() || PointerStride(_handenesses.begin(), _handenesses_stride * _num_vectors) > _handenesses.end()) { return false; } GlImmediatePC im(immediate_renderer(), GL_LINES, _transform); GlImmediatePC::Vertex v = {{0, 0, 0}, {_color.r, _color.g, _color.b, _color.a}}; for (int i = 0; i < _num_vectors; ++i) { const float* position = PointerStride(_positions.data(), _positions_stride * i); v.pos[0] = position[0]; v.pos[1] = position[1]; v.pos[2] = position[2]; im.PushVertex(v); // Compute binormal. const float* p_normal = PointerStride(_normals.data(), _normals_stride * i); const ozz::math::Float3 normal(p_normal[0], p_normal[1], p_normal[2]); const float* p_tangent = PointerStride(_tangents.data(), _tangents_stride * i); const ozz::math::Float3 tangent(p_tangent[0], p_tangent[1], p_tangent[2]); const float* p_handedness = PointerStride(_handenesses.data(), _handenesses_stride * i); // Handedness is used to flip binormal. const ozz::math::Float3 binormal = Cross(normal, tangent) * p_handedness[0]; v.pos[0] = position[0] + binormal.x * _vector_length; v.pos[1] = position[1] + binormal.y * _vector_length; v.pos[2] = position[2] + binormal.z * _vector_length; im.PushVertex(v); } return true; } namespace { const uint8_t kDefaultColorsArray[][4] = { {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}, {255, 255, 255, 255}}; const float kDefaultNormalsArray[][3] = { {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 1.f, 0.f}}; const float kDefaultUVsArray[][2] = { {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}, {0.f, 0.f}}; } // namespace bool RendererImpl::DrawMesh(const Mesh& _mesh, const ozz::math::Float4x4& _transform, const Options& _options) { if (_options.wireframe) { #ifndef EMSCRIPTEN GL(PolygonMode(GL_FRONT_AND_BACK, GL_LINE)); #endif // EMSCRIPTEN } const int vertex_count = _mesh.vertex_count(); const GLsizei positions_offset = 0; const GLsizei positions_stride = sizeof(float) * ozz::sample::Mesh::Part::kPositionsCpnts; const GLsizei positions_size = vertex_count * positions_stride; const GLsizei normals_offset = positions_offset + positions_size; const GLsizei normals_stride = sizeof(float) * ozz::sample::Mesh::Part::kNormalsCpnts; const GLsizei normals_size = vertex_count * normals_stride; // Colors will be filled with white if _options.colors is false. const GLsizei colors_offset = normals_offset + normals_size; const GLsizei colors_stride = sizeof(uint8_t) * ozz::sample::Mesh::Part::kColorsCpnts; const GLsizei colors_size = vertex_count * colors_stride; // Uvs are skipped if _options.texture is false. const GLsizei uvs_offset = colors_offset + colors_size; const GLsizei uvs_stride = _options.texture ? sizeof(float) * ozz::sample::Mesh::Part::kUVsCpnts : 0; const GLsizei uvs_size = vertex_count * uvs_stride; // Reallocate vertex buffer. const GLsizei vbo_size = positions_size + normals_size + colors_size + uvs_size; GL(BindBuffer(GL_ARRAY_BUFFER, dynamic_array_bo_)); GL(BufferData(GL_ARRAY_BUFFER, vbo_size, nullptr, GL_STREAM_DRAW)); // Iterate mesh parts and fills vbo. size_t vertex_offset = 0; for (size_t i = 0; i < _mesh.parts.size(); ++i) { const Mesh::Part& part = _mesh.parts[i]; const size_t part_vertex_count = part.vertex_count(); // Handles positions. GL(BufferSubData( GL_ARRAY_BUFFER, positions_offset + vertex_offset * positions_stride, part_vertex_count * positions_stride, array_begin(part.positions))); // Handles normals. const size_t part_normal_count = part.normals.size() / ozz::sample::Mesh::Part::kNormalsCpnts; if (part_vertex_count == part_normal_count) { // Optimal path used when the right number of normals is provided. GL(BufferSubData( GL_ARRAY_BUFFER, normals_offset + vertex_offset * normals_stride, part_normal_count * normals_stride, array_begin(part.normals))); } else { // Un-optimal path used when the right number of normals is not provided. static_assert(sizeof(kDefaultNormalsArray[0]) == normals_stride, "Stride mismatch"); for (size_t j = 0; j < part_vertex_count; j += OZZ_ARRAY_SIZE(kDefaultNormalsArray)) { const size_t this_loop_count = math::Min( OZZ_ARRAY_SIZE(kDefaultNormalsArray), part_vertex_count - j); GL(BufferSubData(GL_ARRAY_BUFFER, normals_offset + (vertex_offset + j) * normals_stride, normals_stride * this_loop_count, kDefaultNormalsArray)); } } // Handles colors. const size_t part_color_count = part.colors.size() / ozz::sample::Mesh::Part::kColorsCpnts; if (_options.colors && part_vertex_count == part_color_count) { // Optimal path used when the right number of colors is provided. GL(BufferSubData( GL_ARRAY_BUFFER, colors_offset + vertex_offset * colors_stride, part_color_count * colors_stride, array_begin(part.colors))); } else { // Un-optimal path used when the right number of colors is not provided. static_assert(sizeof(kDefaultColorsArray[0]) == colors_stride, "Stride mismatch"); for (size_t j = 0; j < part_vertex_count; j += OZZ_ARRAY_SIZE(kDefaultColorsArray)) { const size_t this_loop_count = math::Min( OZZ_ARRAY_SIZE(kDefaultColorsArray), part_vertex_count - j); GL(BufferSubData(GL_ARRAY_BUFFER, colors_offset + (vertex_offset + j) * colors_stride, colors_stride * this_loop_count, kDefaultColorsArray)); } } // Handles uvs. if (_options.texture) { const size_t part_uvs_count = part.uvs.size() / ozz::sample::Mesh::Part::kUVsCpnts; if (part_vertex_count == part_uvs_count) { // Optimal path used when the right number of uvs is provided. GL(BufferSubData(GL_ARRAY_BUFFER, uvs_offset + vertex_offset * uvs_stride, part_uvs_count * uvs_stride, array_begin(part.uvs))); } else { // Un-optimal path used when the right number of uvs is not provided. assert(sizeof(kDefaultUVsArray[0]) == uvs_stride); for (size_t j = 0; j < part_vertex_count; j += OZZ_ARRAY_SIZE(kDefaultUVsArray)) { const size_t this_loop_count = math::Min( OZZ_ARRAY_SIZE(kDefaultUVsArray), part_vertex_count - j); GL(BufferSubData(GL_ARRAY_BUFFER, uvs_offset + (vertex_offset + j) * uvs_stride, uvs_stride * this_loop_count, kDefaultUVsArray)); } } } // Computes next loop offset. vertex_offset += part_vertex_count; } // Binds shader with this array buffer, depending on rendering options. Shader* shader = nullptr; if (_options.texture) { ambient_textured_shader->Bind(_transform, camera()->view_proj(), positions_stride, positions_offset, normals_stride, normals_offset, colors_stride, colors_offset, uvs_stride, uvs_offset); shader = ambient_textured_shader.get(); // Binds default texture GL(BindTexture(GL_TEXTURE_2D, checkered_texture_)); } else { ambient_shader->Bind(_transform, camera()->view_proj(), positions_stride, positions_offset, normals_stride, normals_offset, colors_stride, colors_offset); shader = ambient_shader.get(); } // Maps the index dynamic buffer and update it. GL(BindBuffer(GL_ELEMENT_ARRAY_BUFFER, dynamic_index_bo_)); const Mesh::TriangleIndices& indices = _mesh.triangle_indices; GL(BufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(Mesh::TriangleIndices::value_type), array_begin(indices), GL_STREAM_DRAW)); // Draws the mesh. static_assert(sizeof(Mesh::TriangleIndices::value_type) == 2, "Expects 2 bytes indices."); GL(DrawElements(GL_TRIANGLES, static_cast(indices.size()), GL_UNSIGNED_SHORT, 0)); // Unbinds. GL(BindBuffer(GL_ARRAY_BUFFER, 0)); GL(BindTexture(GL_TEXTURE_2D, 0)); GL(BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); shader->Unbind(); if (_options.wireframe) { #ifndef EMSCRIPTEN GL(PolygonMode(GL_FRONT_AND_BACK, GL_FILL)); #endif // EMSCRIPTEN } // Renders debug normals. if (_options.normals) { for (size_t i = 0; i < _mesh.parts.size(); ++i) { const Mesh::Part& part = _mesh.parts[i]; DrawVectors(make_span(part.positions), ozz::sample::Mesh::Part::kPositionsCpnts * sizeof(float), make_span(part.normals), ozz::sample::Mesh::Part::kNormalsCpnts * sizeof(float), part.vertex_count(), .03f, ozz::sample::kGreen, _transform); } } // Renders debug tangents. if (_options.tangents) { for (size_t i = 0; i < _mesh.parts.size(); ++i) { const Mesh::Part& part = _mesh.parts[i]; if (part.normals.size() != 0) { DrawVectors(make_span(part.positions), ozz::sample::Mesh::Part::kPositionsCpnts * sizeof(float), make_span(part.tangents), ozz::sample::Mesh::Part::kTangentsCpnts * sizeof(float), part.vertex_count(), .03f, ozz::sample::kRed, _transform); } } } // Renders debug binormals. if (_options.binormals) { for (size_t i = 0; i < _mesh.parts.size(); ++i) { const Mesh::Part& part = _mesh.parts[i]; if (part.normals.size() != 0 && part.tangents.size() != 0) { DrawBinormals( make_span(part.positions), ozz::sample::Mesh::Part::kPositionsCpnts * sizeof(float), make_span(part.normals), ozz::sample::Mesh::Part::kNormalsCpnts * sizeof(float), make_span(part.tangents), ozz::sample::Mesh::Part::kTangentsCpnts * sizeof(float), ozz::span(&part.tangents[3], part.tangents.size()), ozz::sample::Mesh::Part::kTangentsCpnts * sizeof(float), part.vertex_count(), .03f, ozz::sample::kBlue, _transform); } } } return true; } bool RendererImpl::DrawSkinnedMesh( const Mesh& _mesh, const span _skinning_matrices, const ozz::math::Float4x4& _transform, const Options& _options) { // Forward to DrawMesh function is skinning is disabled. if (_options.skip_skinning) { return DrawMesh(_mesh, _transform, _options); } if (_options.wireframe) { #ifndef EMSCRIPTEN GL(PolygonMode(GL_FRONT_AND_BACK, GL_LINE)); #endif // EMSCRIPTEN } const int vertex_count = _mesh.vertex_count(); // Positions and normals are interleaved to improve caching while executing // skinning job. const GLsizei positions_offset = 0; const GLsizei normals_offset = sizeof(float) * 3; const GLsizei tangents_offset = sizeof(float) * 6; const GLsizei positions_stride = sizeof(float) * 9; const GLsizei normals_stride = positions_stride; const GLsizei tangents_stride = positions_stride; const GLsizei skinned_data_size = vertex_count * positions_stride; // Colors and uvs are contiguous. They aren't transformed, so they can be // directly copied from source mesh which is non-interleaved as-well. // Colors will be filled with white if _options.colors is false. // UVs will be skipped if _options.textured is false. const GLsizei colors_offset = skinned_data_size; const GLsizei colors_stride = sizeof(uint8_t) * 4; const GLsizei colors_size = vertex_count * colors_stride; const GLsizei uvs_offset = colors_offset + colors_size; const GLsizei uvs_stride = _options.texture ? sizeof(float) * 2 : 0; const GLsizei uvs_size = vertex_count * uvs_stride; const GLsizei fixed_data_size = colors_size + uvs_size; // Reallocate vertex buffer. const GLsizei vbo_size = skinned_data_size + fixed_data_size; void* vbo_map = scratch_buffer_.Resize(vbo_size); // Iterate mesh parts and fills vbo. // Runs a skinning job per mesh part. Triangle indices are shared // across parts. size_t processed_vertex_count = 0; for (size_t i = 0; i < _mesh.parts.size(); ++i) { const ozz::sample::Mesh::Part& part = _mesh.parts[i]; // Skip this iteration if no vertex. const size_t part_vertex_count = part.positions.size() / 3; if (part_vertex_count == 0) { continue; } // Fills the job. ozz::geometry::SkinningJob skinning_job; skinning_job.vertex_count = static_cast(part_vertex_count); const int part_influences_count = part.influences_count(); // Clamps joints influence count according to the option. skinning_job.influences_count = part_influences_count; // Setup skinning matrices, that came from the animation stage before being // multiplied by inverse model-space bind-pose. skinning_job.joint_matrices = _skinning_matrices; // Setup joint's indices. skinning_job.joint_indices = make_span(part.joint_indices); skinning_job.joint_indices_stride = sizeof(uint16_t) * part_influences_count; // Setup joint's weights. if (part_influences_count > 1) { skinning_job.joint_weights = make_span(part.joint_weights); skinning_job.joint_weights_stride = sizeof(float) * (part_influences_count - 1); } // Setup input positions, coming from the loaded mesh. skinning_job.in_positions = make_span(part.positions); skinning_job.in_positions_stride = sizeof(float) * ozz::sample::Mesh::Part::kPositionsCpnts; // Setup output positions, coming from the rendering output mesh buffers. // We need to offset the buffer every loop. float* out_positions_begin = reinterpret_cast(ozz::PointerStride( vbo_map, positions_offset + processed_vertex_count * positions_stride)); float* out_positions_end = ozz::PointerStride( out_positions_begin, part_vertex_count * positions_stride); skinning_job.out_positions = {out_positions_begin, out_positions_end}; skinning_job.out_positions_stride = positions_stride; // Setup normals if input are provided. float* out_normal_begin = reinterpret_cast(ozz::PointerStride( vbo_map, normals_offset + processed_vertex_count * normals_stride)); float* out_normal_end = ozz::PointerStride( out_normal_begin, part_vertex_count * normals_stride); if (part.normals.size() / ozz::sample::Mesh::Part::kNormalsCpnts == part_vertex_count) { // Setup input normals, coming from the loaded mesh. skinning_job.in_normals = make_span(part.normals); skinning_job.in_normals_stride = sizeof(float) * ozz::sample::Mesh::Part::kNormalsCpnts; // Setup output normals, coming from the rendering output mesh buffers. // We need to offset the buffer every loop. skinning_job.out_normals = {out_normal_begin, out_normal_end}; skinning_job.out_normals_stride = normals_stride; } else { // Fills output with default normals. for (float* normal = out_normal_begin; normal < out_normal_end; normal = ozz::PointerStride(normal, normals_stride)) { normal[0] = 0.f; normal[1] = 1.f; normal[2] = 0.f; } } // Setup tangents if input are provided. float* out_tangent_begin = reinterpret_cast(ozz::PointerStride( vbo_map, tangents_offset + processed_vertex_count * tangents_stride)); float* out_tangent_end = ozz::PointerStride( out_tangent_begin, part_vertex_count * tangents_stride); if (part.tangents.size() / ozz::sample::Mesh::Part::kTangentsCpnts == part_vertex_count) { // Setup input tangents, coming from the loaded mesh. skinning_job.in_tangents = make_span(part.tangents); skinning_job.in_tangents_stride = sizeof(float) * ozz::sample::Mesh::Part::kTangentsCpnts; // Setup output tangents, coming from the rendering output mesh buffers. // We need to offset the buffer every loop. skinning_job.out_tangents = {out_tangent_begin, out_tangent_end}; skinning_job.out_tangents_stride = tangents_stride; } else { // Fills output with default tangents. for (float* tangent = out_tangent_begin; tangent < out_tangent_end; tangent = ozz::PointerStride(tangent, tangents_stride)) { tangent[0] = 1.f; tangent[1] = 0.f; tangent[2] = 0.f; } } // Execute the job, which should succeed unless a parameter is invalid. if (!skinning_job.Run()) { return false; } // Renders debug normals. if (_options.normals && skinning_job.out_normals.size() > 0) { DrawVectors(skinning_job.out_positions, skinning_job.out_positions_stride, skinning_job.out_normals, skinning_job.out_normals_stride, skinning_job.vertex_count, .03f, ozz::sample::kGreen, _transform); } // Renders debug tangents. if (_options.tangents && skinning_job.out_tangents.size() > 0) { DrawVectors(skinning_job.out_positions, skinning_job.out_positions_stride, skinning_job.out_tangents, skinning_job.out_tangents_stride, skinning_job.vertex_count, .03f, ozz::sample::kRed, _transform); } // Renders debug binormals. if (_options.binormals && skinning_job.out_normals.size() > 0 && skinning_job.out_tangents.size() > 0) { DrawBinormals(skinning_job.out_positions, skinning_job.out_positions_stride, skinning_job.out_normals, skinning_job.out_normals_stride, skinning_job.out_tangents, skinning_job.out_tangents_stride, ozz::span(skinning_job.in_tangents.begin() + 3, skinning_job.in_tangents.end() + 3), skinning_job.in_tangents_stride, skinning_job.vertex_count, .03f, ozz::sample::kBlue, _transform); } // Handles colors which aren't affected by skinning. if (_options.colors && part_vertex_count == part.colors.size() / ozz::sample::Mesh::Part::kColorsCpnts) { // Optimal path used when the right number of colors is provided. memcpy( ozz::PointerStride( vbo_map, colors_offset + processed_vertex_count * colors_stride), array_begin(part.colors), part_vertex_count * colors_stride); } else { // Un-optimal path used when the right number of colors is not provided. static_assert(sizeof(kDefaultColorsArray[0]) == colors_stride, "Stride mismatch"); for (size_t j = 0; j < part_vertex_count; j += OZZ_ARRAY_SIZE(kDefaultColorsArray)) { const size_t this_loop_count = math::Min( OZZ_ARRAY_SIZE(kDefaultColorsArray), part_vertex_count - j); memcpy(ozz::PointerStride( vbo_map, colors_offset + (processed_vertex_count + j) * colors_stride), kDefaultColorsArray, colors_stride * this_loop_count); } } // Copies uvs which aren't affected by skinning. if (_options.texture) { if (part_vertex_count == part.uvs.size() / ozz::sample::Mesh::Part::kUVsCpnts) { // Optimal path used when the right number of uvs is provided. memcpy(ozz::PointerStride( vbo_map, uvs_offset + processed_vertex_count * uvs_stride), array_begin(part.uvs), part_vertex_count * uvs_stride); } else { // Un-optimal path used when the right number of uvs is not provided. assert(sizeof(kDefaultUVsArray[0]) == uvs_stride); for (size_t j = 0; j < part_vertex_count; j += OZZ_ARRAY_SIZE(kDefaultUVsArray)) { const size_t this_loop_count = math::Min( OZZ_ARRAY_SIZE(kDefaultUVsArray), part_vertex_count - j); memcpy(ozz::PointerStride( vbo_map, uvs_offset + (processed_vertex_count + j) * uvs_stride), kDefaultUVsArray, uvs_stride * this_loop_count); } } } // Some more vertices were processed. processed_vertex_count += part_vertex_count; } // Updates dynamic vertex buffer with skinned data. GL(BindBuffer(GL_ARRAY_BUFFER, dynamic_array_bo_)); GL(BufferData(GL_ARRAY_BUFFER, vbo_size, nullptr, GL_STREAM_DRAW)); GL(BufferSubData(GL_ARRAY_BUFFER, 0, vbo_size, vbo_map)); // Binds shader with this array buffer, depending on rendering options. Shader* shader = nullptr; if (_options.texture) { ambient_textured_shader->Bind(_transform, camera()->view_proj(), positions_stride, positions_offset, normals_stride, normals_offset, colors_stride, colors_offset, uvs_stride, uvs_offset); shader = ambient_textured_shader.get(); // Binds default texture GL(BindTexture(GL_TEXTURE_2D, checkered_texture_)); } else { ambient_shader->Bind(_transform, camera()->view_proj(), positions_stride, positions_offset, normals_stride, normals_offset, colors_stride, colors_offset); shader = ambient_shader.get(); } // Maps the index dynamic buffer and update it. GL(BindBuffer(GL_ELEMENT_ARRAY_BUFFER, dynamic_index_bo_)); const Mesh::TriangleIndices& indices = _mesh.triangle_indices; GL(BufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(Mesh::TriangleIndices::value_type), array_begin(indices), GL_STREAM_DRAW)); // Draws the mesh. static_assert(sizeof(Mesh::TriangleIndices::value_type) == 2, "Expects 2 bytes indices."); GL(DrawElements(GL_TRIANGLES, static_cast(indices.size()), GL_UNSIGNED_SHORT, 0)); // Unbinds. GL(BindBuffer(GL_ARRAY_BUFFER, 0)); GL(BindTexture(GL_TEXTURE_2D, 0)); GL(BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); shader->Unbind(); if (_options.wireframe) { #ifndef EMSCRIPTEN GL(PolygonMode(GL_FRONT_AND_BACK, GL_FILL)); #endif // EMSCRIPTEN } return true; } // Helper macro used to initialize extension function pointer. #define OZZ_INIT_GL_EXT_N(_fct, _fct_name, _fct_type, _success) \ do { \ _fct = reinterpret_cast<_fct_type>(glfwGetProcAddress(_fct_name)); \ if (_fct == nullptr) { \ log::Err() << "Unable to install " _fct_name " function." << std::endl; \ _success &= false; \ } \ \ } while (void(0), 0) #define OZZ_INIT_GL_EXT(_fct, _fct_type, _success) \ OZZ_INIT_GL_EXT_N(_fct, #_fct, _fct_type, _success) bool RendererImpl::InitOpenGLExtensions() { bool optional_success = true; bool success = true; // aka mandatory extensions #ifdef OZZ_GL_VERSION_1_5_EXT OZZ_INIT_GL_EXT(glBindBuffer, PFNGLBINDBUFFERPROC, success); OZZ_INIT_GL_EXT(glDeleteBuffers, PFNGLDELETEBUFFERSPROC, success); OZZ_INIT_GL_EXT(glGenBuffers, PFNGLGENBUFFERSPROC, success); OZZ_INIT_GL_EXT(glIsBuffer, PFNGLISBUFFERPROC, success); OZZ_INIT_GL_EXT(glBufferData, PFNGLBUFFERDATAPROC, success); OZZ_INIT_GL_EXT(glBufferSubData, PFNGLBUFFERSUBDATAPROC, success); OZZ_INIT_GL_EXT(glMapBuffer, PFNGLMAPBUFFERPROC, optional_success); OZZ_INIT_GL_EXT(glUnmapBuffer, PFNGLUNMAPBUFFERPROC, optional_success); OZZ_INIT_GL_EXT(glGetBufferParameteriv, PFNGLGETBUFFERPARAMETERIVPROC, success); #endif // OZZ_GL_VERSION_1_5_EXT #ifdef OZZ_GL_VERSION_2_0_EXT OZZ_INIT_GL_EXT(glAttachShader, PFNGLATTACHSHADERPROC, success); OZZ_INIT_GL_EXT(glBindAttribLocation, PFNGLBINDATTRIBLOCATIONPROC, success); OZZ_INIT_GL_EXT(glCompileShader, PFNGLCOMPILESHADERPROC, success); OZZ_INIT_GL_EXT(glCreateProgram, PFNGLCREATEPROGRAMPROC, success); OZZ_INIT_GL_EXT(glCreateShader, PFNGLCREATESHADERPROC, success); OZZ_INIT_GL_EXT(glDeleteProgram, PFNGLDELETEPROGRAMPROC, success); OZZ_INIT_GL_EXT(glDeleteShader, PFNGLDELETESHADERPROC, success); OZZ_INIT_GL_EXT(glDetachShader, PFNGLDETACHSHADERPROC, success); OZZ_INIT_GL_EXT(glDisableVertexAttribArray, PFNGLDISABLEVERTEXATTRIBARRAYPROC, success); OZZ_INIT_GL_EXT(glEnableVertexAttribArray, PFNGLENABLEVERTEXATTRIBARRAYPROC, success); OZZ_INIT_GL_EXT(glGetActiveAttrib, PFNGLGETACTIVEATTRIBPROC, success); OZZ_INIT_GL_EXT(glGetActiveUniform, PFNGLGETACTIVEUNIFORMPROC, success); OZZ_INIT_GL_EXT(glGetAttachedShaders, PFNGLGETATTACHEDSHADERSPROC, success); OZZ_INIT_GL_EXT(glGetAttribLocation, PFNGLGETATTRIBLOCATIONPROC, success); OZZ_INIT_GL_EXT(glGetProgramiv, PFNGLGETPROGRAMIVPROC, success); OZZ_INIT_GL_EXT(glGetProgramInfoLog, PFNGLGETPROGRAMINFOLOGPROC, success); OZZ_INIT_GL_EXT(glGetShaderiv, PFNGLGETSHADERIVPROC, success); OZZ_INIT_GL_EXT(glGetShaderInfoLog, PFNGLGETSHADERINFOLOGPROC, success); OZZ_INIT_GL_EXT(glGetShaderSource, PFNGLGETSHADERSOURCEPROC, success); OZZ_INIT_GL_EXT(glGetUniformLocation, PFNGLGETUNIFORMLOCATIONPROC, success); OZZ_INIT_GL_EXT(glGetUniformfv, PFNGLGETUNIFORMFVPROC, success); OZZ_INIT_GL_EXT(glGetUniformiv, PFNGLGETUNIFORMIVPROC, success); OZZ_INIT_GL_EXT(glGetVertexAttribfv, PFNGLGETVERTEXATTRIBFVPROC, success); OZZ_INIT_GL_EXT(glGetVertexAttribiv, PFNGLGETVERTEXATTRIBIVPROC, success); OZZ_INIT_GL_EXT(glGetVertexAttribPointerv, PFNGLGETVERTEXATTRIBPOINTERVPROC, success); OZZ_INIT_GL_EXT(glIsProgram, PFNGLISPROGRAMPROC, success); OZZ_INIT_GL_EXT(glIsShader, PFNGLISSHADERPROC, success); OZZ_INIT_GL_EXT(glLinkProgram, PFNGLLINKPROGRAMPROC, success); OZZ_INIT_GL_EXT(glShaderSource, PFNGLSHADERSOURCEPROC, success); OZZ_INIT_GL_EXT(glUseProgram, PFNGLUSEPROGRAMPROC, success); OZZ_INIT_GL_EXT(glUniform1f, PFNGLUNIFORM1FPROC, success); OZZ_INIT_GL_EXT(glUniform2f, PFNGLUNIFORM2FPROC, success); OZZ_INIT_GL_EXT(glUniform3f, PFNGLUNIFORM3FPROC, success); OZZ_INIT_GL_EXT(glUniform4f, PFNGLUNIFORM4FPROC, success); OZZ_INIT_GL_EXT(glUniform1i, PFNGLUNIFORM1IPROC, success); OZZ_INIT_GL_EXT(glUniform2i, PFNGLUNIFORM2IPROC, success); OZZ_INIT_GL_EXT(glUniform3i, PFNGLUNIFORM3IPROC, success); OZZ_INIT_GL_EXT(glUniform4i, PFNGLUNIFORM4IPROC, success); OZZ_INIT_GL_EXT(glUniform1fv, PFNGLUNIFORM1FVPROC, success); OZZ_INIT_GL_EXT(glUniform2fv, PFNGLUNIFORM2FVPROC, success); OZZ_INIT_GL_EXT(glUniform3fv, PFNGLUNIFORM3FVPROC, success); OZZ_INIT_GL_EXT(glUniform4fv, PFNGLUNIFORM4FVPROC, success); OZZ_INIT_GL_EXT(glUniformMatrix2fv, PFNGLUNIFORMMATRIX2FVPROC, success); OZZ_INIT_GL_EXT(glUniformMatrix3fv, PFNGLUNIFORMMATRIX3FVPROC, success); OZZ_INIT_GL_EXT(glUniformMatrix4fv, PFNGLUNIFORMMATRIX4FVPROC, success); OZZ_INIT_GL_EXT(glValidateProgram, PFNGLVALIDATEPROGRAMPROC, success); OZZ_INIT_GL_EXT(glVertexAttrib1f, PFNGLVERTEXATTRIB1FPROC, success); OZZ_INIT_GL_EXT(glVertexAttrib1fv, PFNGLVERTEXATTRIB1FVPROC, success); OZZ_INIT_GL_EXT(glVertexAttrib2f, PFNGLVERTEXATTRIB2FPROC, success); OZZ_INIT_GL_EXT(glVertexAttrib2fv, PFNGLVERTEXATTRIB2FVPROC, success); OZZ_INIT_GL_EXT(glVertexAttrib3f, PFNGLVERTEXATTRIB3FPROC, success); OZZ_INIT_GL_EXT(glVertexAttrib3fv, PFNGLVERTEXATTRIB3FVPROC, success); OZZ_INIT_GL_EXT(glVertexAttrib4f, PFNGLVERTEXATTRIB4FPROC, success); OZZ_INIT_GL_EXT(glVertexAttrib4fv, PFNGLVERTEXATTRIB4FVPROC, success); OZZ_INIT_GL_EXT(glVertexAttribPointer, PFNGLVERTEXATTRIBPOINTERPROC, success); #endif // OZZ_GL_VERSION_2_0_EXT if (!success) { log::Err() << "Failed to initialize all mandatory GL extensions." << std::endl; return false; } if (!optional_success) { log::Log() << "Failed to initialize some optional GL extensions." << std::endl; } GL_ARB_instanced_arrays_supported = glfwExtensionSupported("GL_ARB_instanced_arrays") != 0; if (GL_ARB_instanced_arrays_supported) { log::Log() << "Optional GL_ARB_instanced_arrays extensions found." << std::endl; success = true; OZZ_INIT_GL_EXT_N(glVertexAttribDivisor_, "glVertexAttribDivisorARB", PFNGLVERTEXATTRIBDIVISORARBPROC, success); OZZ_INIT_GL_EXT_N(glDrawArraysInstanced_, "glDrawArraysInstancedARB", PFNGLDRAWARRAYSINSTANCEDARBPROC, success); OZZ_INIT_GL_EXT_N(glDrawElementsInstanced_, "glDrawElementsInstancedARB", PFNGLDRAWELEMENTSINSTANCEDARBPROC, success); if (!success) { log::Err() << "Failed to setup GL_ARB_instanced_arrays, feature is disabled." << std::endl; GL_ARB_instanced_arrays_supported = false; } } else { log::Log() << "Optional GL_ARB_instanced_arrays extensions not found." << std::endl; } return true; } RendererImpl::ScratchBuffer::ScratchBuffer() : buffer_(nullptr), size_(0) {} RendererImpl::ScratchBuffer::~ScratchBuffer() { memory::default_allocator()->Deallocate(buffer_); } void* RendererImpl::ScratchBuffer::Resize(size_t _size) { if (_size > size_) { size_ = _size; memory::default_allocator()->Deallocate(buffer_); buffer_ = memory::default_allocator()->Allocate(_size, 16); } return buffer_; } } // namespace internal } // namespace sample } // namespace ozz // Helper macro used to declare extension function pointer. #define OZZ_DECL_GL_EXT(_fct, _fct_type) _fct_type _fct = nullptr #ifdef OZZ_GL_VERSION_1_5_EXT OZZ_DECL_GL_EXT(glBindBuffer, PFNGLBINDBUFFERPROC); OZZ_DECL_GL_EXT(glDeleteBuffers, PFNGLDELETEBUFFERSPROC); OZZ_DECL_GL_EXT(glGenBuffers, PFNGLGENBUFFERSPROC); OZZ_DECL_GL_EXT(glIsBuffer, PFNGLISBUFFERPROC); OZZ_DECL_GL_EXT(glBufferData, PFNGLBUFFERDATAPROC); OZZ_DECL_GL_EXT(glBufferSubData, PFNGLBUFFERSUBDATAPROC); OZZ_DECL_GL_EXT(glGetBufferSubData, PFNGLGETBUFFERSUBDATAPROC); OZZ_DECL_GL_EXT(glMapBuffer, PFNGLMAPBUFFERPROC); OZZ_DECL_GL_EXT(glUnmapBuffer, PFNGLUNMAPBUFFERPROC); OZZ_DECL_GL_EXT(glGetBufferParameteriv, PFNGLGETBUFFERPARAMETERIVPROC); OZZ_DECL_GL_EXT(glGetBufferPointerv, PFNGLGETBUFFERPOINTERVPROC); #endif // OZZ_GL_VERSION_1_5_EXT #ifdef OZZ_GL_VERSION_2_0_EXT OZZ_DECL_GL_EXT(glAttachShader, PFNGLATTACHSHADERPROC); OZZ_DECL_GL_EXT(glBindAttribLocation, PFNGLBINDATTRIBLOCATIONPROC); OZZ_DECL_GL_EXT(glCompileShader, PFNGLCOMPILESHADERPROC); OZZ_DECL_GL_EXT(glCreateProgram, PFNGLCREATEPROGRAMPROC); OZZ_DECL_GL_EXT(glCreateShader, PFNGLCREATESHADERPROC); OZZ_DECL_GL_EXT(glDeleteProgram, PFNGLDELETEPROGRAMPROC); OZZ_DECL_GL_EXT(glDeleteShader, PFNGLDELETESHADERPROC); OZZ_DECL_GL_EXT(glDetachShader, PFNGLDETACHSHADERPROC); OZZ_DECL_GL_EXT(glDisableVertexAttribArray, PFNGLDISABLEVERTEXATTRIBARRAYPROC); OZZ_DECL_GL_EXT(glEnableVertexAttribArray, PFNGLENABLEVERTEXATTRIBARRAYPROC); OZZ_DECL_GL_EXT(glGetActiveAttrib, PFNGLGETACTIVEATTRIBPROC); OZZ_DECL_GL_EXT(glGetActiveUniform, PFNGLGETACTIVEUNIFORMPROC); OZZ_DECL_GL_EXT(glGetAttachedShaders, PFNGLGETATTACHEDSHADERSPROC); OZZ_DECL_GL_EXT(glGetAttribLocation, PFNGLGETATTRIBLOCATIONPROC); OZZ_DECL_GL_EXT(glGetProgramiv, PFNGLGETPROGRAMIVPROC); OZZ_DECL_GL_EXT(glGetProgramInfoLog, PFNGLGETPROGRAMINFOLOGPROC); OZZ_DECL_GL_EXT(glGetShaderiv, PFNGLGETSHADERIVPROC); OZZ_DECL_GL_EXT(glGetShaderInfoLog, PFNGLGETSHADERINFOLOGPROC); OZZ_DECL_GL_EXT(glGetShaderSource, PFNGLGETSHADERSOURCEPROC); OZZ_DECL_GL_EXT(glGetUniformLocation, PFNGLGETUNIFORMLOCATIONPROC); OZZ_DECL_GL_EXT(glGetUniformfv, PFNGLGETUNIFORMFVPROC); OZZ_DECL_GL_EXT(glGetUniformiv, PFNGLGETUNIFORMIVPROC); OZZ_DECL_GL_EXT(glGetVertexAttribdv, PFNGLGETVERTEXATTRIBDVPROC); OZZ_DECL_GL_EXT(glGetVertexAttribfv, PFNGLGETVERTEXATTRIBFVPROC); OZZ_DECL_GL_EXT(glGetVertexAttribiv, PFNGLGETVERTEXATTRIBIVPROC); OZZ_DECL_GL_EXT(glGetVertexAttribPointerv, PFNGLGETVERTEXATTRIBPOINTERVPROC); OZZ_DECL_GL_EXT(glIsProgram, PFNGLISPROGRAMPROC); OZZ_DECL_GL_EXT(glIsShader, PFNGLISSHADERPROC); OZZ_DECL_GL_EXT(glLinkProgram, PFNGLLINKPROGRAMPROC); OZZ_DECL_GL_EXT(glShaderSource, PFNGLSHADERSOURCEPROC); OZZ_DECL_GL_EXT(glUseProgram, PFNGLUSEPROGRAMPROC); OZZ_DECL_GL_EXT(glUniform1f, PFNGLUNIFORM1FPROC); OZZ_DECL_GL_EXT(glUniform2f, PFNGLUNIFORM2FPROC); OZZ_DECL_GL_EXT(glUniform3f, PFNGLUNIFORM3FPROC); OZZ_DECL_GL_EXT(glUniform4f, PFNGLUNIFORM4FPROC); OZZ_DECL_GL_EXT(glUniform1i, PFNGLUNIFORM1IPROC); OZZ_DECL_GL_EXT(glUniform2i, PFNGLUNIFORM2IPROC); OZZ_DECL_GL_EXT(glUniform3i, PFNGLUNIFORM3IPROC); OZZ_DECL_GL_EXT(glUniform4i, PFNGLUNIFORM4IPROC); OZZ_DECL_GL_EXT(glUniform1fv, PFNGLUNIFORM1FVPROC); OZZ_DECL_GL_EXT(glUniform2fv, PFNGLUNIFORM2FVPROC); OZZ_DECL_GL_EXT(glUniform3fv, PFNGLUNIFORM3FVPROC); OZZ_DECL_GL_EXT(glUniform4fv, PFNGLUNIFORM4FVPROC); OZZ_DECL_GL_EXT(glUniformMatrix2fv, PFNGLUNIFORMMATRIX2FVPROC); OZZ_DECL_GL_EXT(glUniformMatrix3fv, PFNGLUNIFORMMATRIX3FVPROC); OZZ_DECL_GL_EXT(glUniformMatrix4fv, PFNGLUNIFORMMATRIX4FVPROC); OZZ_DECL_GL_EXT(glValidateProgram, PFNGLVALIDATEPROGRAMPROC); OZZ_DECL_GL_EXT(glVertexAttrib1f, PFNGLVERTEXATTRIB1FPROC); OZZ_DECL_GL_EXT(glVertexAttrib1fv, PFNGLVERTEXATTRIB1FVPROC); OZZ_DECL_GL_EXT(glVertexAttrib2f, PFNGLVERTEXATTRIB2FPROC); OZZ_DECL_GL_EXT(glVertexAttrib2fv, PFNGLVERTEXATTRIB2FVPROC); OZZ_DECL_GL_EXT(glVertexAttrib3f, PFNGLVERTEXATTRIB3FPROC); OZZ_DECL_GL_EXT(glVertexAttrib3fv, PFNGLVERTEXATTRIB3FVPROC); OZZ_DECL_GL_EXT(glVertexAttrib4f, PFNGLVERTEXATTRIB4FPROC); OZZ_DECL_GL_EXT(glVertexAttrib4fv, PFNGLVERTEXATTRIB4FVPROC); OZZ_DECL_GL_EXT(glVertexAttribPointer, PFNGLVERTEXATTRIBPOINTERPROC); #endif // OZZ_GL_VERSION_2_0_EXT bool GL_ARB_instanced_arrays_supported = false; OZZ_DECL_GL_EXT(glVertexAttribDivisor_, PFNGLVERTEXATTRIBDIVISORARBPROC); OZZ_DECL_GL_EXT(glDrawArraysInstanced_, PFNGLDRAWARRAYSINSTANCEDARBPROC); OZZ_DECL_GL_EXT(glDrawElementsInstanced_, PFNGLDRAWELEMENTSINSTANCEDARBPROC);