// // Lol Engine // // Copyright © 2010—2015 Sam Hocevar <sam@hocevar.net> // © 2014—2015 Benjamin “Touky” Huet <huet.benjamin@gmail.com> // // Lol Engine is free software. It comes without any warranty, to // the extent permitted by applicable law. You can redistribute it // and/or modify it under the terms of the Do What the Fuck You Want // to Public License, Version 2, as published by the WTFPL Task Force. // See http://www.wtfpl.net/ for more details. // #include <lol/engine-internal.h> #include <cstdlib> #if defined(_WIN32) # define WIN32_LEAN_AND_MEAN 1 # include <windows.h> # undef WIN32_LEAN_AND_MEAN #endif #include "lolgl.h" LOLFX_RESOURCE_DECLARE(tile); LOLFX_RESOURCE_DECLARE(palette); LOLFX_RESOURCE_DECLARE(line); LOLFX_RESOURCE_DECLARE(postprocess); namespace lol { /* * The global g_scenes object, initialised by Video::Init */ array<Scene*> Scene::g_scenes; /* * A quick and dirty Tile structure for 2D blits */ struct Tile { mat4 m_model; TileSet *m_tileset; int m_id; }; //----------------------------------------------------------------------------- static array<SceneDisplay*> m_scene_displays; /* * Public SceneDisplay class */ void SceneDisplay::Add(SceneDisplay* display) { m_scene_displays << display; } int SceneDisplay::GetCount() { return m_scene_displays.count(); } SceneDisplay* SceneDisplay::GetDisplay(int index) { ASSERT(0 <= index && index < m_scene_displays.count(), "invalid display index %d", index); return m_scene_displays[index]; } void SceneDisplay::DestroyAll() { for (SceneDisplay* display : m_scene_displays) delete display; m_scene_displays.empty(); } /* ------------------------------------------------ */ void SceneDisplay::Enable() { // TODO: PROFILER STUFF } void SceneDisplay::Disable() { // TODO: PROFILER STUFF } /* * Primitive implementation class */ void PrimitiveSource::Render(Scene& scene) { UNUSED(scene); } void PrimitiveRenderer::Render(Scene& scene, PrimitiveSource* primitive) { UNUSED(scene); UNUSED(primitive); } /* * Scene implementation class */ class SceneData { friend class Scene; private: SceneData() { /* TODO: FIX THAT */ ASSERT(!(m_used_id & ((uint64_t)1 << 63)), "Too many scenes !!!!"); m_mask_id = m_used_id; m_used_id = m_used_id << 1; } /* Mask ID */ /* TODO: Do a mask class that handles more than 64 slots */ static uint64_t m_used_id; uint64_t m_mask_id = 0; /* Scene display: if none has been set to the scene, * the default one created by the app will be used */ SceneDisplay* m_display = nullptr; /** Back buffer: where to render to. */ Framebuffer *m_backbuffer = nullptr; struct postprocess { Shader *m_shader = nullptr; VertexBuffer *m_vbo = nullptr; VertexDeclaration *m_vdecl = nullptr; ShaderUniform m_texture; ShaderAttrib m_coord; } m_pp; /* Sources are shared by all scenes. * Renderers are scene-dependent. They get the primitive in the identical * slot to render with the given scene. * Primitives and renderers will be kept until: * - Updated by entity * - Marked Fire&Forget * - Scene is destroyed */ map<uintptr_t, array<PrimitiveRenderer*>> m_prim_renderers; static map<uintptr_t, array<PrimitiveSource*>> m_prim_sources; static mutex m_prim_mutex; Camera *m_default_cam; array<Camera *> m_camera_stack; /* Old line API <P0, P1, COLOR, TIME, MASK> */ struct line_api { float m_time, m_segment_size; vec4 m_color; array<vec3, vec3, vec4, float, int, bool, bool> m_lines; int m_mask, m_debug_mask; Shader *m_shader; VertexDeclaration *m_vdecl; } m_line_api; /* The old tiles API */ struct tile_api { int m_cam; array<Tile> m_tiles; array<Tile> m_palettes; array<Light *> m_lights; Shader *m_shader; Shader *m_palette_shader; VertexDeclaration *m_vdecl; array<VertexBuffer *> m_bufs; } m_tile_api; }; uint64_t SceneData::m_used_id = 1; map<uintptr_t, array<PrimitiveSource*>> SceneData::m_prim_sources; mutex SceneData::m_prim_mutex; /* * Public Scene class */ Scene::Scene(ivec2 size) : data(new SceneData()) { data->m_backbuffer = new Framebuffer(size); data->m_pp.m_shader = Shader::Create(LOLFX_RESOURCE_NAME(postprocess)); data->m_pp.m_coord = data->m_pp.m_shader->GetAttribLocation(VertexUsage::Position, 0); data->m_pp.m_vdecl = new VertexDeclaration(VertexStream<vec2>(VertexUsage::Position)); data->m_pp.m_texture = data->m_pp.m_shader->GetUniformLocation("u_texture"); array<vec2> quad { vec2( 1.0, 1.0), vec2(-1.0, -1.0), vec2( 1.0, -1.0), vec2(-1.0, -1.0), vec2( 1.0, 1.0), vec2(-1.0, 1.0), }; data->m_pp.m_vbo = new VertexBuffer(quad.bytes()); void *vertices = data->m_pp.m_vbo->Lock(0, 0); memcpy(vertices, quad.data(), quad.bytes()); data->m_pp.m_vbo->Unlock(); /* Create a default orthographic camera, in case the user doesn’t. */ data->m_default_cam = new Camera(); mat4 proj = mat4::ortho(0.f, (float)size.x, 0.f, (float)size.y, -1000.f, 1000.f); data->m_default_cam->SetProjection(proj); PushCamera(data->m_default_cam); data->m_tile_api.m_cam = -1; data->m_tile_api.m_shader = 0; data->m_tile_api.m_palette_shader = 0; data->m_tile_api.m_vdecl = new VertexDeclaration(VertexStream<vec3>(VertexUsage::Position), VertexStream<vec2>(VertexUsage::TexCoord)); data->m_line_api.m_shader = 0; data->m_line_api.m_vdecl = new VertexDeclaration(VertexStream<vec4,vec4>(VertexUsage::Position, VertexUsage::Color)); data->m_line_api.m_debug_mask = 1; SetLineTime(); SetLineMask(); SetLineSegmentSize(); SetLineColor(); } //----------------------------------------------------------------------------- Scene::~Scene() { PopCamera(data->m_default_cam); /* FIXME: this must be done while the GL context is still active. * Change the code architecture to make sure of that. */ /* FIXME: also, make sure we do not add code to Reset() that will * reallocate stuff */ Reset(); delete data->m_line_api.m_vdecl; delete data->m_tile_api.m_vdecl; delete data; } //----------------------------------------------------------------------------- void Scene::AddNew(ivec2 size) { Scene::g_scenes << new Scene(size); } void Scene::DestroyScene(Scene* scene) { Scene::g_scenes.remove_item(scene); delete scene; } void Scene::DestroyAll() { while (Scene::g_scenes.count()) delete Scene::g_scenes.pop(); } int Scene::GetCount() { return g_scenes.count(); } //----------------------------------------------------------------------------- bool Scene::IsReady(int index) { return 0 <= index && index < g_scenes.count() && !!g_scenes[index]; } //----------------------------------------------------------------------------- Scene& Scene::GetScene(int index) { ASSERT(0 <= index && index < g_scenes.count() && !!g_scenes[index], "Trying to get a non-existent scene %d", index); return *g_scenes[index]; } //----------------------------------------------------------------------------- void Scene::Link(Entity* entity) { entity->m_scene_mask |= data->m_mask_id; } //----------------------------------------------------------------------------- bool Scene::IsRelevant(Entity* entity) { return !!(entity->m_scene_mask & data->m_mask_id); } //----------------------------------------------------------------------------- Camera* Scene::GetCamera(int cam_idx) { ASSERT(!!data, "Trying to access a non-ready scene"); return (0 <= cam_idx && cam_idx < data->m_camera_stack.count()) ? data->m_camera_stack[cam_idx] : data->m_camera_stack.last(); } //----------------------------------------------------------------------------- int Scene::PushCamera(Camera *cam) { ASSERT(!!data, "Trying to access a non-ready scene"); Ticker::Ref(cam); data->m_camera_stack.push(cam); return (int)data->m_camera_stack.count() - 1; } //----------------------------------------------------------------------------- void Scene::PopCamera(Camera *cam) { ASSERT(!!data, "Trying to access a non-ready scene"); /* Parse from the end because that’s probably where we’ll find * our camera first. */ for (int i = data->m_camera_stack.count(); i--;) { if (data->m_camera_stack[i] == cam) { Ticker::Unref(cam); data->m_camera_stack.remove(i); return; } } ASSERT(false, "trying to pop a nonexistent camera from the scene"); } //----------------------------------------------------------------------------- void Scene::SetTileCam(int cam_idx) { ASSERT(!!data, "Trying to access a non-ready scene"); data->m_tile_api.m_cam = cam_idx; } //----------------------------------------------------------------------------- void Scene::Reset() { ASSERT(!!data, "Trying to access a non-ready scene"); /* New scenegraph: Release fire&forget primitives */ array<uintptr_t> keys = data->m_prim_renderers.keys(); for (uintptr_t key : keys) { for (int idx = 0; idx < data->m_prim_renderers[key].count(); ++idx) if (data->m_prim_renderers[key][idx]->m_fire_and_forget) ReleasePrimitiveRenderer(idx--, key); } for (int i = 0; i < data->m_tile_api.m_bufs.count(); i++) delete data->m_tile_api.m_bufs[i]; data->m_tile_api.m_bufs.empty(); data->m_tile_api.m_lights.empty(); } //---- Primitive source stuff ------------------------------------------------- int Scene::HasPrimitiveSource(uintptr_t key) { int count; SceneData::m_prim_mutex.lock(); { count = SceneData::m_prim_sources[key].count(); } SceneData::m_prim_mutex.unlock(); return count; } int Scene::AddPrimitiveSource(uintptr_t key, PrimitiveSource* source) { int count; SceneData::m_prim_mutex.lock(); { count = SceneData::m_prim_sources[key].count(); SceneData::m_prim_sources[key].push(source); } SceneData::m_prim_mutex.unlock(); return count; } void Scene::SetPrimitiveSource(int index, uintptr_t key, PrimitiveSource* source) { ASSERT(source); ASSERT(index >= 0); PrimitiveSource* old = nullptr; SceneData::m_prim_mutex.lock(); { if (index < SceneData::m_prim_sources[key].count()) old = SceneData::m_prim_sources[key][index]; else SceneData::m_prim_sources[key].resize(index + 1); SceneData::m_prim_sources[key][index] = source; } SceneData::m_prim_mutex.unlock(); // Delete old AFTER having released the lock delete old; } void Scene::ReleasePrimitiveSource(int index, uintptr_t key) { PrimitiveSource* old = nullptr; SceneData::m_prim_mutex.lock(); { ASSERT(0 <= index && index < SceneData::m_prim_sources[key].count()); old = SceneData::m_prim_sources[key][index]; SceneData::m_prim_sources[key].remove(index); } SceneData::m_prim_mutex.unlock(); // Delete old AFTER having released the lock delete old; } void Scene::ReleaseAllPrimitiveSources(uintptr_t key) { array<PrimitiveSource*> oldies; SceneData::m_prim_mutex.lock(); { oldies.reserve(SceneData::m_prim_sources[key].count()); for (PrimitiveSource* source : SceneData::m_prim_sources[key]) oldies << source; SceneData::m_prim_sources[key].empty(); } SceneData::m_prim_mutex.unlock(); // Delete oldies AFTER having released the lock for (PrimitiveSource* old : oldies) delete old; } //---- Primitive renderer stuff ----------------------------------------------- int Scene::HasPrimitiveRenderer(uintptr_t key) { return data->m_prim_renderers[key].count(); } void Scene::AddPrimitiveRenderer(uintptr_t key, PrimitiveRenderer* renderer) { renderer->m_fire_and_forget = true; data->m_prim_renderers[key].push(renderer); } void Scene::SetPrimitiveRenderer(int index, uintptr_t key, PrimitiveRenderer* renderer) { ASSERT(renderer); ASSERT(index >= 0); if (index < data->m_prim_renderers[key].count()) { ASSERT(data->m_prim_renderers[key][index]); delete data->m_prim_renderers[key][index]; } else data->m_prim_renderers[key].resize(index + 1); data->m_prim_renderers[key][index] = renderer; } void Scene::ReleasePrimitiveRenderer(int index, uintptr_t key) { ASSERT(0 <= index && index < data->m_prim_renderers[key].count()); delete data->m_prim_renderers[key][index]; data->m_prim_renderers[key].remove(index); } void Scene::ReleaseAllPrimitiveRenderers(uintptr_t key) { for (PrimitiveRenderer*& renderer : data->m_prim_renderers[key]) { delete renderer; renderer = nullptr; } } //----------------------------------------------------------------------------- void Scene::AddTile(TileSet *tileset, int id, vec3 pos, int o, vec2 scale, float radians) { ASSERT(id < tileset->GetTileCount()); ivec2 size = tileset->GetTileSize(id); mat4 model = mat4::translate(pos) * mat4::scale(scale.x, scale.y, 1.f) * mat4::translate(size.x * 0.5f, size.y * 0.5f, 0.f) * mat4::rotate(scale.x * scale.y < 0 ? radians : -radians, vec3::axis_z); AddTile(tileset, id, model); } void Scene::AddTile(TileSet *tileset, int id, mat4 model) { ASSERT(id < tileset->GetTileCount()); Tile t; t.m_model = model; t.m_tileset = tileset; t.m_id = id; if (tileset->GetPalette()) data->m_tile_api.m_palettes.push(t); else data->m_tile_api.m_tiles.push(t); } //----------------------------------------------------------------------------- void Scene::SetLineTime(float new_time) { ASSERT(!!data, "Trying to access a non-ready scene"); data->m_line_api.m_time = new_time; } void Scene::SetLineMask(int new_mask) { ASSERT(!!data, "Trying to access a non-ready scene"); data->m_line_api.m_mask = new_mask; } void Scene::SetLineSegmentSize(float new_segment_size) { ASSERT(!!data, "Trying to access a non-ready scene"); data->m_line_api.m_segment_size = new_segment_size; } void Scene::SetLineColor(vec4 new_color) { ASSERT(!!data, "Trying to access a non-ready scene"); data->m_line_api.m_color = new_color; } //----------------------------------------------------------------------------- float Scene::GetLineSegmentSize() { ASSERT(!!data, "Trying to access a non-ready scene"); return data->m_line_api.m_segment_size; } vec4 Scene::GetLineColor() { ASSERT(!!data, "Trying to access a non-ready scene"); return data->m_line_api.m_color; } //----------------------------------------------------------------------------- void Scene::AddLine(vec3 a, vec3 b, vec4 color) { ASSERT(!!data, "Trying to access a non-ready scene"); data->m_line_api.m_lines.push(a, b, color, data->m_line_api.m_time, data->m_line_api.m_mask, false, false); } //----------------------------------------------------------------------------- void Scene::AddLight(Light *l) { ASSERT(!!data, "Trying to access a non-ready scene"); data->m_tile_api.m_lights.push(l); } //----------------------------------------------------------------------------- array<Light *> const &Scene::GetLights() { ASSERT(!!data, "Trying to access a non-ready scene"); return data->m_tile_api.m_lights; } //----------------------------------------------------------------------------- void Scene::SetDisplay(SceneDisplay* display) { data->m_display = display; } //----------------------------------------------------------------------------- void Scene::EnableDisplay() { // If no display has been set, use the default one if (!data->m_display) SetDisplay(SceneDisplay::GetDisplay()); data->m_display->Enable(); } void Scene::DisableDisplay() { ASSERT(data->m_display); data->m_display->Disable(); } /* Render everything that the scene contains */ void Scene::render(float seconds) { /* First render into the offline buffer */ data->m_backbuffer->Bind(); { RenderContext rc; rc.SetClearColor(vec4(0.f, 0.f, 0.f, 1.f)); rc.SetClearDepth(1.f); Renderer::Get()->Clear(ClearMask::Color | ClearMask::Depth); // FIXME: get rid of the delta time argument render_primitives(); render_tiles(); render_lines(seconds); } data->m_backbuffer->Unbind(); /* Now blit the offline buffer */ data->m_pp.m_shader->Bind(); data->m_pp.m_shader->SetUniform(data->m_pp.m_texture, data->m_backbuffer->GetTextureUniform(), 0); data->m_pp.m_vdecl->SetStream(data->m_pp.m_vbo, data->m_pp.m_coord); data->m_pp.m_vdecl->Bind(); data->m_pp.m_vdecl->DrawElements(MeshPrimitive::Triangles, 0, 6); data->m_pp.m_vdecl->Unbind(); data->m_pp.m_shader->Unbind(); } //----------------------------------------------------------------------------- void Scene::render_primitives() { ASSERT(!!data, "Trying to access a non-ready scene"); /* FIXME: Temp fix for mesh having no render context*/ RenderContext rc; rc.SetCullMode(CullMode::Clockwise); rc.SetDepthFunc(DepthFunc::LessOrEqual); /* new scenegraph */ array<uintptr_t> keys = data->m_prim_renderers.keys(); for (uintptr_t key : keys) { for (int idx = 0; idx < data->m_prim_renderers[key].count(); ++idx) { /* TODO: Not sure if thread compliant */ data->m_prim_renderers[key][idx]->Render(*this, idx < SceneData::m_prim_sources[key].count() ? SceneData::m_prim_sources[key][idx] : nullptr); } } } //----------------------------------------------------------------------------- void Scene::render_tiles() // XXX: rename to Blit() { ASSERT(!!data, "Trying to access a non-ready scene"); RenderContext rc; /* Early test if nothing needs to be rendered */ if (!data->m_tile_api.m_tiles.count() && !data->m_tile_api.m_palettes.count()) return; /* FIXME: we disable culling for now because we don’t have a reliable * way to know which side is facing the camera. */ rc.SetCullMode(CullMode::Disabled); rc.SetDepthFunc(DepthFunc::LessOrEqual); rc.SetBlendFunc(BlendFunc::SrcAlpha, BlendFunc::OneMinusSrcAlpha); rc.SetBlendEquation(BlendEquation::Add, BlendEquation::Max); rc.SetAlphaFunc(AlphaFunc::GreaterOrEqual, 0.01f); #if (defined USE_GLEW || defined HAVE_GL_2X) && !defined HAVE_GLES_2X glEnable(GL_TEXTURE_2D); #endif if (!data->m_tile_api.m_shader) data->m_tile_api.m_shader = Shader::Create(LOLFX_RESOURCE_NAME(tile)); if (!data->m_tile_api.m_palette_shader) data->m_tile_api.m_palette_shader = Shader::Create(LOLFX_RESOURCE_NAME(palette)); for (int p = 0; p < 2; p++) { Shader *shader = (p == 0) ? data->m_tile_api.m_shader : data->m_tile_api.m_palette_shader; array<Tile>& tiles = (p == 0) ? data->m_tile_api.m_tiles : data->m_tile_api.m_palettes; if (tiles.count() == 0) continue; ShaderUniform uni_mat, uni_tex, uni_pal, uni_texsize; ShaderAttrib attr_pos, attr_tex; attr_pos = shader->GetAttribLocation(VertexUsage::Position, 0); attr_tex = shader->GetAttribLocation(VertexUsage::TexCoord, 0); shader->Bind(); uni_mat = shader->GetUniformLocation("u_projection"); shader->SetUniform(uni_mat, GetCamera(data->m_tile_api.m_cam)->GetProjection()); uni_mat = shader->GetUniformLocation("u_view"); shader->SetUniform(uni_mat, GetCamera(data->m_tile_api.m_cam)->GetView()); uni_mat = shader->GetUniformLocation("u_model"); shader->SetUniform(uni_mat, mat4(1.f)); uni_tex = shader->GetUniformLocation("u_texture"); uni_pal = data->m_tile_api.m_palette_shader->GetUniformLocation("u_palette"); uni_texsize = shader->GetUniformLocation("u_texsize"); for (int buf = 0, i = 0, n; i < tiles.count(); i = n, buf += 2) { /* Count how many quads will be needed */ for (n = i + 1; n < tiles.count(); n++) if (tiles[i].m_tileset != tiles[n].m_tileset) break; /* Create a vertex array object */ VertexBuffer *vb1 = new VertexBuffer(6 * (n - i) * sizeof(vec3)); vec3 *vertex = (vec3 *)vb1->Lock(0, 0); VertexBuffer *vb2 = new VertexBuffer(6 * (n - i) * sizeof(vec2)); vec2 *texture = (vec2 *)vb2->Lock(0, 0); data->m_tile_api.m_bufs.push(vb1); data->m_tile_api.m_bufs.push(vb2); for (int j = i; j < n; j++) { tiles[i].m_tileset->BlitTile(tiles[j].m_id, tiles[j].m_model, vertex + 6 * (j - i), texture + 6 * (j - i)); } vb1->Unlock(); vb2->Unlock(); /* Bind texture */ if (tiles[i].m_tileset->GetPalette()) { if (tiles[i].m_tileset->GetTexture()) shader->SetUniform(uni_tex, tiles[i].m_tileset->GetTexture()->GetTextureUniform(), 0); if (tiles[i].m_tileset->GetPalette()->GetTexture()) shader->SetUniform(uni_pal, tiles[i].m_tileset->GetPalette()->GetTexture()->GetTextureUniform(), 1); } else { shader->SetUniform(uni_tex, 0); if (tiles[i].m_tileset->GetTexture()) shader->SetUniform(uni_tex, tiles[i].m_tileset->GetTexture()->GetTextureUniform(), 0); tiles[i].m_tileset->Bind(); } shader->SetUniform(uni_texsize, (vec2)tiles[i].m_tileset->GetTextureSize()); /* Bind vertex and texture coordinate buffers */ data->m_tile_api.m_vdecl->Bind(); data->m_tile_api.m_vdecl->SetStream(vb1, attr_pos); data->m_tile_api.m_vdecl->SetStream(vb2, attr_tex); /* Draw arrays */ data->m_tile_api.m_vdecl->DrawElements(MeshPrimitive::Triangles, 0, (n - i) * 6); data->m_tile_api.m_vdecl->Unbind(); tiles[i].m_tileset->Unbind(); } tiles.empty(); shader->Unbind(); } #if (defined USE_GLEW || defined HAVE_GL_2X) && !defined HAVE_GLES_2X glDisable(GL_TEXTURE_2D); #endif } //----------------------------------------------------------------------------- // FIXME: get rid of the delta time argument // XXX: rename to Blit() void Scene::render_lines(float seconds) { ASSERT(!!data, "Trying to access a non-ready scene"); RenderContext rc; if (!data->m_line_api.m_lines.count()) return; rc.SetDepthFunc(DepthFunc::LessOrEqual); rc.SetBlendFunc(BlendFunc::SrcAlpha, BlendFunc::OneMinusSrcAlpha); rc.SetBlendEquation(BlendEquation::Add, BlendEquation::Max); rc.SetAlphaFunc(AlphaFunc::GreaterOrEqual, 0.01f); int linecount = (int)data->m_line_api.m_lines.count(); if (!data->m_line_api.m_shader) data->m_line_api.m_shader = Shader::Create(LOLFX_RESOURCE_NAME(line)); array<vec4, vec4, vec4, vec4> buff; buff.resize(linecount); int real_linecount = 0; mat4 const inv_view_proj = inverse(GetCamera()->GetProjection() * GetCamera()->GetView()); for (int i = 0; i < linecount; i++) { if (data->m_line_api.m_lines[i].m5 & data->m_line_api.m_debug_mask) { buff[real_linecount].m1 = vec4(data->m_line_api.m_lines[i].m1, (float)data->m_line_api.m_lines[i].m6); buff[real_linecount].m2 = data->m_line_api.m_lines[i].m3; buff[real_linecount].m3 = vec4(data->m_line_api.m_lines[i].m2, (float)data->m_line_api.m_lines[i].m7); buff[real_linecount].m4 = data->m_line_api.m_lines[i].m3; real_linecount++; } data->m_line_api.m_lines[i].m4 -= seconds; if (data->m_line_api.m_lines[i].m4 < 0.f) { data->m_line_api.m_lines.remove_swap(i--); linecount--; } } VertexBuffer *vb = new VertexBuffer(buff.bytes()); float *vertex = (float *)vb->Lock(0, 0); memcpy(vertex, buff.data(), buff.bytes()); vb->Unlock(); data->m_line_api.m_shader->Bind(); ShaderUniform uni_mat, uni_tex; ShaderAttrib attr_pos, attr_col; attr_pos = data->m_line_api.m_shader->GetAttribLocation(VertexUsage::Position, 0); attr_col = data->m_line_api.m_shader->GetAttribLocation(VertexUsage::Color, 0); data->m_line_api.m_shader->Bind(); uni_mat = data->m_line_api.m_shader->GetUniformLocation("u_projection"); data->m_line_api.m_shader->SetUniform(uni_mat, GetCamera()->GetProjection()); uni_mat = data->m_line_api.m_shader->GetUniformLocation("u_view"); data->m_line_api.m_shader->SetUniform(uni_mat, GetCamera()->GetView()); data->m_line_api.m_vdecl->Bind(); data->m_line_api.m_vdecl->SetStream(vb, attr_pos, attr_col); data->m_line_api.m_vdecl->DrawElements(MeshPrimitive::Lines, 0, 2 * real_linecount); data->m_line_api.m_vdecl->Unbind(); data->m_line_api.m_shader->Unbind(); //data->m_line_api.m_lines.empty(); delete vb; } } /* namespace lol */