Low level graphics programming with tz::gl - harrand/Topaz-2 GitHub Wiki
Topaz-2 always uses OpenGL as its rendering API. If you're familiar with OpenGL, you should find tz::gl somewhat similar to what you're used to. The API was designed so that it would need minimal changes to become rendering-API-agnostic, hence why there are some minor differences in nomenclature and convention compared to other OpenGL programs.
-
tz::gl::Object
~= glVertexArray Object (VAO) -
tz::gl::IBuffer
~= Generic Buffer Object Interface (Includes VBOs, IBOs, SSBOs, UBOs etc...) -
tz::gl::Buffer<T>
~= Various specialisations of IBuffers -
tz::gl::Frame
~= FrameBuffer Object (FBO) -
tz::gl::ShaderProgram
~= GLSL Shader Program for the GPU -
tz::gl::Shader
~= GLSL Shader Component (can be a vertex-shader, fragment-shader etc...) -
tz::gl::ShaderCompiler
~= Represents a generic shader compiler. At present only exists as the driver-provided shader compiler, but custom shader compilation may be offered in the future. -
tz::gl::ShaderPreprocessor
~= Represents a generic shader preprocessor. This is the driving force behind TZGLP (tz::gl Preprocessing) -
tz::gl::Format
~= Vertex Attribute -
tz::gl::Texture
~= OpenGL Texture -
tz::gl::TextureSentinel
~= Runtime-checking class responsible for sanity-checking OpenGL Bindless Texture usage in tz::gl::Textures. One instance exists globally which all tz::gl::Textures call into. Responsible for hard-asserting to protect against the hazards of bindless texture misuse. -
tz::gl::Image<PixelType>
~= Generalisation of bitmap images -
tz::gl::IndexSnippet
~= Represents a contiguous region of indices within an existing IBO. -
tz::gl::IndexSnippetList
~= Represents a collection of zero or more IndexSnippets. -
tz::gl::Manager
~= Specialised, managedtz::gl::Object
using a standardised buffer format for rendering. Should be preferred over normal Objects unless an alternative format is required (such as ImGui support).
Note: There are often aliases for specific buffer types. For example:
tz::gl::Buffer<tz::gl::BufferType::Array>
== tz::gl::VertexBuffer
== tz::gl::VBO
Buffers represent various blocks of VRAM. Buffers are specialised for a specific purpose. For example, a tz::gl::VBO
has a contiguous block of memory available in VRAM which should store vertex data. "Vertex data" is a fairly arbitrary term -- I'm talking 3D positions, texture-coordinates, normals, tangents, bitangents etc...
By default, Buffers are unmanaged.
Managed buffers are special in that they have additional functionality useful for managing buffer data. Managed Buffers can mark parts of its data-store using regions. It can also provide an offset and byte-size for a given region to access the region data. Note that for terminal managed buffers, this doesn't even require any data copying.
Regions label a subset of buffer data with a name. The subset is a contiguous block of memory. They are very useful for remembering where certain data attributes can be found, especially if one buffer will be storing many meshes.
Objects are glorified collections of Buffers (be they managed or not). At present, a Buffer can be owned by upto one Object at a time.
Some Buffers require a parent Object to hold it to function properly. Some do not. It is planned to have an additional entry in the inheritance hierarchy to account for this (i.e the creation of some DependentBuffer
subclass of IBuffer
). As an example, tz::gl::VBO
s are expected to have an Object parent, but tz::gl::SSBO
s need no Object parent to work properly.
Managers are a special tz::gl::Object which automatically creates multiple data and index buffers (one terminal, one nonterminal) and can easily add new meshes. It can also easily provide the mesh data back again via taking a copy (stored in an OwningBlock). It also contains an 'attrib' method which can easily specify vertex attribute information to OpenGL simply by taking a region name.
You are free to choose whether to add data into a terminal or nonterminal buffer for optimisation reasons. However the onus is on you to remember which type of buffer you stored your data into:
-
tz::gl::Data::Static
represents the nonterminal vertex buffer -
tz::gl::Data::Dynamic
represents the terminal vertex buffer -
tz::gl::Indices::Static
represents the nonterminal index buffer -
tz::gl::Indices::Dynamic
represents the terminal index buffer.
Note: I don't expect any use-cases for a terminal index buffer to arise anytime soon, so that specific attribute is likely to become deprecated soon.
#include "core/core.hpp"
#include "core/debug/print.hpp"
#include "gl/shader.hpp"
#include "gl/shader_compiler.hpp"
#include "gl/object.hpp"
#include "gl/buffer.hpp"
#include "gl/frame.hpp"
#include "GLFW/glfw3.h"
const char *vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(0.8f, 0.15f, 0.0f, 1.0f);\n"
"}\n\0";
int main()
{
// Minimalist Graphics Demo.
tz::core::initialise("Topaz Graphics Demo");
{
tz::gl::ShaderCompiler cpl;
tz::gl::ShaderProgram prg;
tz::gl::Shader* vs = prg.emplace(tz::gl::ShaderType::Vertex);
vs->upload_source(vertexShaderSource);
tz::gl::Shader* fs = prg.emplace(tz::gl::ShaderType::Fragment);
fs->upload_source(fragmentShaderSource);
cpl.compile(*vs);
cpl.compile(*fs);
cpl.link(prg);
const float vertices[] = {
-0.5f, -0.5f, 0.0f, // left
0.5f, -0.5f, 0.0f, // right
0.0f, 0.5f, 0.0f // top
};
tz::gl::Object o;
std::size_t vbo_id = o.emplace_buffer<tz::gl::BufferType::Array>();
tz::gl::VBO* vbo = o.get<tz::gl::BufferType::Array>(vbo_id);
vbo->terminal_resize(sizeof(vertices));
tz::mem::UniformPool<float> vertex_pool = vbo->map_pool<float>();
for(std::size_t i = 0; i < vertex_pool.capacity(); i++)
vertex_pool.set(i, vertices[i]);
auto add_pos = [&vertex_pool](float x, float y, float z)
{
vertex_pool[0] += x;
vertex_pool[3] += x;
vertex_pool[6] += x;
vertex_pool[1] += y;
vertex_pool[4] += y;
vertex_pool[7] += y;
vertex_pool[2] += z;
vertex_pool[5] += z;
vertex_pool[8] += z;
};
o.format(vbo_id, tz::gl::fmt::three_floats);
tz::core::IWindow& wnd = tz::core::get().window();
wnd.register_this();
wnd.emplace_custom_key_listener([&add_pos](tz::input::KeyPressEvent e)
{
switch(e.key)
{
case GLFW_KEY_W:
add_pos(0.0f, 0.05f, 0.0f);
tz::debug_printf("moving forward.\n");
break;
case GLFW_KEY_S:
add_pos(0.0f, -0.05f, 0.0f);
tz::debug_printf("moving backward.\n");
break;
case GLFW_KEY_A:
add_pos(-0.05f, 0.0f, 0.0f);
tz::debug_printf("moving left\n");
break;
case GLFW_KEY_D:
add_pos(0.05f, 0.0f, 0.0f);
tz::debug_printf("moving right\n");
break;
}
});
glClearColor(0.0f, 0.3f, 0.15f, 1.0f);
while(!wnd.is_close_requested())
{
wnd.get_frame()->clear();
prg.bind();
o.bind();
o.render(1);
wnd.update();
tz::core::update();
}
}
tz::core::terminate();
}