Low level graphics programming with tz::gl - harrand/Topaz-2 GitHub Wiki

Summary

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.

Aliases

  • 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, managed tz::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

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

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

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

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::VBOs are expected to have an Object parent, but tz::gl::SSBOs need no Object parent to work properly.

Managers

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.

Example: Minimalist Graphics Demo

#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();
}

Output

Topaz Graphics Demo

⚠️ **GitHub.com Fallback** ⚠️