diff --git a/BUILDING b/BUILDING new file mode 100644 index 0000000..bee6666 --- /dev/null +++ b/BUILDING @@ -0,0 +1 @@ +make all diff --git a/DEPENDENCIES b/DEPENDENCIES new file mode 100644 index 0000000..674b64e --- /dev/null +++ b/DEPENDENCIES @@ -0,0 +1,6 @@ +C/C++ libraries (dev versions): + +- X11 +- GLX +- GLESv2 +- GLM diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..89e81ca --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ +SOURCES = cpp/opengl/render_context_glx.cpp cpp/opengl/render_opengles.cpp cpp/math.cpp cpp/render.cpp +OUTPUTFILE = engine.so + +CXXFLAGS = -fpic -std=c++20 -Wall -Werror -O2 -flto -fomit-frame-pointer -ffast-math -funroll-loops -fno-rtti -fno-exceptions + +.PHONY: all +all: clean $(OUTPUTFILE) + find . -name "*.o" -type f -delete + +.PHONY: clean +clean: + rm -f $(OUTPUTFILE) + find . -name "*.o" -type f -delete + +$(OUTPUTFILE): $(subst .cpp,.o,$(SOURCES)) + $(CXX) -shared $(CXXFLAGS) -s -o $@ $^ -lGLESv2 -lGLX -lX11 diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..8ef28dd --- /dev/null +++ b/__init__.py @@ -0,0 +1,350 @@ +# Copyright (C) 2022 RozK +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import ctypes +import struct +from array import array + +_lib = ctypes.cdll.LoadLibrary("./engine/engine.so") + +def _flag(x): + return 1 << x + +INPUT_IDENTITY = 0 +INPUT_VIEW_POSITION = 1 +INPUT_VIEW_ORIENTATION = 2 + +TEXTURE_FORMAT_SRGB8_A8 = 0 +TEXTURE_FORMAT_RGBA8 = 1 +TEXTURE_FORMAT_RGB10_A2 = 2 +TEXTURE_FORMAT_32F = 3 + +TEXTURE_FORMAT_TYPECODE = ('B', 'B', 'I', 'f') +TEXTURE_FORMAT_NELEMS = (4, 4, 1, 1) + +TEXTURE_FLAG_3D = _flag(0) +TEXTURE_FLAG_MIPMAPS = _flag(1) +TEXTURE_FLAG_MIN_NEAREST = 0 +TEXTURE_FLAG_MIN_LINEAR = _flag(2) +TEXTURE_FLAG_MAG_NEAREST = 0 +TEXTURE_FLAG_MAG_LINEAR = _flag(3) + +VERTEX_FORMAT_VEC2_FLOAT = 1 +VERTEX_FORMAT_VEC2_USHORT = 2 +VERTEX_FORMAT_VEC3_FLOAT = 3 +VERTEX_FORMAT_VEC3_INT10 = 4 + +def vertex_format(*format): + return array('B', format).tobytes() + +INSTANCE_FLAG_SPAWNED = _flag(0) +INSTANCE_FLAG_VISIBLE = _flag(1) + +BATCH_MAX_SIZE = 65536 + +BATCH_TRANSLATION_FORMAT_FLOAT = 0 +BATCH_TRANSLATION_FORMAT_SHORT = 1 + +BATCH_ORIENTATION_FORMAT_NONE = 0 +BATCH_ORIENTATION_FORMAT_FLOAT = 1 +BATCH_ORIENTATION_FORMAT_INT10 = 2 + +#TODO: remove from engine +vec2_zero = (0.0, 0.0) +vec3_zero = (0.0, 0.0, 0.0) +vec3_right = (1.0, 0.0, 0.0) +vec3_forward = (0.0, 1.0, 0.0) +vec3_up = (0.0, 0.0, 1.0) +vec4_zero = (0.0, 0.0, 0.0, 1.0) +mat3_identity = ( + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0) +mat4_identity = ( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 1.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0) + +def vec2(v = vec2_zero): + assert len(v) == 2 + return array('f', v) + +def vec3(v = vec3_zero): + assert len(v) == 3 + return array('f', v) + +def vec4(v = vec4_zero): + assert len(v) == 4 + return array('f', v) + +def mat3(m = mat3_identity): + assert len(m) == 9 + return array('f', m) + +def mat4(m = mat4_identity): + assert len(m) == 16 + return array('f', m) + +_vec2_t = (ctypes.c_float * 2) +_vec2 = _vec2_t.from_buffer + +_vec3_t = (ctypes.c_float * 3) +_vec3 = _vec3_t.from_buffer + +_vec4_t = (ctypes.c_float * 4) +_vec4 = _vec4_t.from_buffer + +_mat3_t = (ctypes.c_float * 9) +_mat3 = _mat3_t.from_buffer + +_mat4_t = (ctypes.c_float * 16) +_mat4 = _mat4_t.from_buffer + +def _voidp(x): + return x.buffer_info()[0] + +def _ubytep(x): + assert x.typecode == 'B' + return x.buffer_info()[0] + +def _ushortp(x): + assert x.typecode == 'H' + return x.buffer_info()[0] + +def _uintp(x): + assert x.typecode == 'I' + return x.buffer_info()[0] + +def _floatp(x): + assert x.typecode == 'f' + return x.buffer_info()[0] + +_mat3_rotation = _lib.rk_mat3_rotation +_mat3_rotation.argtypes = ( + _mat3_t, # ret + _vec3_t, # axis + ctypes.c_float) # angle + +def mat3_rotation(ret, axis, angle): + assert len(ret) == 9 and len(axis) == 3 + _mat3_rotation(_mat3(ret), _vec3(axis), angle) + +_mat3_mul_vec3 = _lib.rk_mat3_mul_vec3 +_mat3_mul_vec3.argtypes = ( + _vec3_t, # ret + _mat3_t, # a + _vec3_t) # b + +def mat3_mul_vec3(ret, a, b): + assert len(ret) == 3 and len(a) == 9 and len(b) == 3 + _mat3_mul_vec3(_vec3(ret), _mat3(a), _vec3(b)) + +_mat3_mul_mat3 = _lib.rk_mat3_mul_mat3 +_mat3_mul_mat3.argtypes = ( + _mat3_t, # ret + _mat3_t, # a + _mat3_t) # b + +def mat3_mul_mat3(ret, a, b): + assert len(ret) == 9 and len(a) == 9 and len(b) == 9 + _mat3_mul_mat3(_mat3(ret), _mat3(a), _mat3(b)) + +_mat4_mul_vec4 = _lib.rk_mat4_mul_vec4 +_mat4_mul_vec4.argtypes = ( + _vec4_t, # ret + _mat4_t, # a + _vec4_t) # b + +def mat4_mul_vec4(ret, a, b): + assert len(ret) == 4 and len(a) == 16 and len(b) == 4 + _mat4_mul_vec4(_vec4(ret), _mat4(a), _vec4(b)) + +_mat4_mul_mat4 = _lib.rk_mat4_mul_mat4 +_mat4_mul_mat4.argtypes = ( + _mat4_t, # ret + _mat4_t, # a + _mat4_t) # b + +def mat4_mul_mat4(ret, a, b): + assert len(ret) == 16 and len(a) == 16 and len(b) == 16 + _mat4_mul_mat4(_mat4(ret), _mat4(a), _mat4(b)) + +initialize = _lib.rk_initialize +initialize.restype = ctypes.c_void_p +initialize.argtypes = ( + ctypes.c_char_p,) # name + +_load_shader = _lib.rk_load_shader +_load_shader.restype = ctypes.c_void_p +_load_shader.argtypes = ( + ctypes.c_char_p,) # name + +def load_shader(name): + print("Loading shader", str(name, 'utf-8')); + return _load_shader(name) + +select_shader = _lib.rk_select_shader +select_shader.argtypes = ( + ctypes.c_void_p,) # shader + +resolve_input = _lib.rk_resolve_input +resolve_input.restype = ctypes.c_void_p +resolve_input.argtypes = ( + ctypes.c_char_p,) # name + +set_input_float = _lib.rk_set_input_float +set_input_float.argtypes = ( + ctypes.c_void_p, # input + ctypes.c_float) # value + +_set_input_vec3 = _lib.rk_set_input_vec3 +_set_input_vec3.argtypes = ( + ctypes.c_void_p, # input + _vec3_t, # value + ctypes.c_uint) # mode + +def set_input_vec3(input, value, mode = INPUT_IDENTITY): + assert len(value) == 3 + _set_input_vec3(input, _vec3(value), mode) + +_create_texture = _lib.rk_create_texture +_create_texture.restype = ctypes.c_void_p +_create_texture.argtypes = ( + ctypes.c_uint, # slot + ctypes.c_char_p, # input + ctypes.c_uint, # format + ctypes.c_uint, # width + ctypes.c_uint, # height + ctypes.c_uint, # nlevels + ctypes.c_uint, # flags + ctypes.c_void_p) # pixels + +def create_texture(slot, input, format, width, height, nlevels, flags, pixels): + assert pixels.typecode == TEXTURE_FORMAT_TYPECODE[format] + assert len(pixels) == width * height * max(1, nlevels) * TEXTURE_FORMAT_NELEMS[format] + return _create_texture(slot, input, format, width, height, nlevels, flags, _voidp(pixels)) + +_create_triangles = _lib.rk_create_triangles +_create_triangles.restype = ctypes.c_void_p +_create_triangles.argtypes = ( + ctypes.c_uint, # nvertices + ctypes.c_void_p) # vertices + +def create_triangles(vertices): + assert len(vertices) % 9 == 0 + return _create_triangles(len(vertices) // 3, _floatp(vertices)) + +_create_vertices = _lib.rk_create_vertices +_create_vertices.restype = ctypes.c_void_p +_create_vertices.argtypes = ( + ctypes.c_char_p, # format + ctypes.c_uint, # nvertices + ctypes.c_void_p, # vertices + ctypes.c_uint, # nindices + ctypes.c_void_p) # indices + +def create_vertices(format, nvertices, vertices, indices): + return _create_vertices(format, nvertices, _ubytep(vertices), len(indices), _ushortp(indices)) + +create_batch = _lib.rk_create_batch +create_batch.restype = ctypes.c_void_p +create_batch.argtypes = ( + ctypes.c_uint, # max_size + ctypes.c_uint, # translation_format + ctypes.c_uint) # orientation_format + +set_projection = _lib.rk_set_projection +set_projection.argtypes = ( + ctypes.c_float, # hfov + ctypes.c_float, # ratio + ctypes.c_float, # near + ctypes.c_float) # far + +_set_view = _lib.rk_set_view +_set_view.argtypes = ( + _vec3_t, # position + _vec3_t) # lookat + +def set_view(position, lookat): + assert len(position) == 3 and len(lookat) == 3 + _set_view(_vec3(position), _vec3(lookat)) + +begin_frame = _lib.rk_begin_frame + +select_texture = _lib.rk_select_texture +select_texture.argtypes = ( + ctypes.c_void_p,) # texture + +draw_triangles = _lib.rk_draw_triangles +draw_triangles.argtypes = ( + ctypes.c_void_p,) # triangles + +select_vertices = _lib.rk_select_vertices +select_vertices.argtypes = ( + ctypes.c_void_p,) # vertices + +_draw_batch = _lib.rk_draw_batch +_draw_batch.argtypes = ( + ctypes.c_void_p, # batch + ctypes.c_uint, # size + ctypes.c_void_p, # flags + ctypes.c_void_p, # texlevels + ctypes.c_void_p, # meshes + ctypes.c_void_p, # translations + ctypes.c_void_p) # orientations + +def draw_batch(batch, flags, texlevels, meshes, translations, orientations): + size = len(flags) + assert len(texlevels) == size and len(meshes) == size and len(translations) == size * 3 + assert not orientations or len(orientations) == size * 3 + _draw_batch(batch, size, _ubytep(flags), _ushortp(texlevels), _uintp(meshes), _floatp(translations), + _floatp(orientations) if orientations else None) + +unselect_vertices = _lib.rk_unselect_vertices +unselect_vertices.argtypes = ( + ctypes.c_void_p,) # vertices + +unselect_texture = _lib.rk_unselect_texture +unselect_texture.argtypes = ( + ctypes.c_void_p,) # texture + +unselect_shader = _lib.rk_unselect_shader +unselect_shader.argtypes = ( + ctypes.c_void_p,) # shader + +end_frame = _lib.rk_end_frame + +destroy_batch = _lib.rk_destroy_batch +destroy_batch.argtypes = ( + ctypes.c_void_p,) # batch + +destroy_triangles = _lib.rk_destroy_triangles +destroy_triangles.argtypes = ( + ctypes.c_void_p,) # triangles + +destroy_vertices = _lib.rk_destroy_vertices +destroy_vertices.argtypes = ( + ctypes.c_void_p,) # vertices + +destroy_texture = _lib.rk_destroy_texture +destroy_texture.argtypes = ( + ctypes.c_void_p,) # texture + +destroy_shader = _lib.rk_destroy_shader +destroy_shader.argtypes = ( + ctypes.c_void_p,) # shader + +terminate = _lib.rk_terminate diff --git a/cpp/math.cpp b/cpp/math.cpp new file mode 100644 index 0000000..2d34ff3 --- /dev/null +++ b/cpp/math.cpp @@ -0,0 +1,53 @@ +// Copyright (C) 2022 RozK +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "math.hpp" + +//TODO: benchmark this ctypes interface against pure python maths + +void rk_mat3_rotation( + rk_mat3 & ret, + rk_vec3 const & axis, + float const angle) { + ret = glm::mat3_cast(glm::angleAxis(angle, axis)); +} + +void rk_mat3_mul_vec3( + rk_vec3 & ret, + rk_mat3 const & a, + rk_vec3 const & b) { + ret = a * b; +} + +void rk_mat3_mul_mat3( + rk_mat3 & ret, + rk_mat3 const & a, + rk_mat3 const & b) { + ret = a * b; +} + +void rk_mat4_mul_vec4( + rk_vec4 & ret, + rk_mat4 const & a, + rk_vec4 const & b) { + ret = a * b; +} + +void rk_mat4_mul_mat4( + rk_mat4 & ret, + rk_mat4 const & a, + rk_mat4 const & b) { + ret = a * b; +} diff --git a/cpp/math.hpp b/cpp/math.hpp new file mode 100644 index 0000000..0d82ee3 --- /dev/null +++ b/cpp/math.hpp @@ -0,0 +1,54 @@ +// Copyright (C) 2022 RozK +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef _RK_ENGINE_MATH_H +#define _RK_ENGINE_MATH_H + +#include "types.hpp" +#include +#include + +typedef glm::vec2 rk_vec2; +typedef glm::vec3 rk_vec3; +typedef glm::vec4 rk_vec4; +typedef glm::mat3 rk_mat3; +typedef glm::mat4 rk_mat4; + +RK_EXPORT void rk_mat3_rotation( + rk_mat3 & ret, + rk_vec3 const & axis, + float const angle); + +RK_EXPORT void rk_mat3_mul_vec3( + rk_vec3 & ret, + rk_mat3 const & a, + rk_vec3 const & b); + +RK_EXPORT void rk_mat3_mul_mat3( + rk_mat3 & ret, + rk_mat3 const & a, + rk_mat3 const & b); + +RK_EXPORT void rk_mat4_mul_vec4( + rk_vec4 & ret, + rk_mat4 const & a, + rk_vec4 const & b); + +RK_EXPORT void rk_mat4_mul_mat4( + rk_mat4 & ret, + rk_mat4 const & a, + rk_mat4 const & b); + +#endif // _RK_ENGINE_MATH_H diff --git a/cpp/opengl/render_context.hpp b/cpp/opengl/render_context.hpp new file mode 100644 index 0000000..1d632a0 --- /dev/null +++ b/cpp/opengl/render_context.hpp @@ -0,0 +1,38 @@ +// Copyright (C) 2022 RozK +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef _RK_ENGINE_RENDER_OPENGL_CONTEXT_H +#define _RK_ENGINE_RENDER_OPENGL_CONTEXT_H + +#include "../render.hpp" + +RK_EXPORT rk_window_t rk_create_context( + char const * name, + int profile, + int major, + int minor); + +RK_EXPORT char ** rk_load_shader_source( + char const * filename, + rk_uint * length); + +RK_EXPORT void rk_free_shader_source( + char ** shader, + rk_uint length); + +RK_EXPORT void rk_swap_buffers(); +RK_EXPORT void rk_destroy_context(); + +#endif // _RK_ENGINE_RENDER_OPENGL_CONTEXT_H diff --git a/cpp/opengl/render_context_glx.cpp b/cpp/opengl/render_context_glx.cpp new file mode 100644 index 0000000..884ecd1 --- /dev/null +++ b/cpp/opengl/render_context_glx.cpp @@ -0,0 +1,254 @@ +// Copyright (C) 2022 RozK +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +// Adapted from https://www.khronos.org/opengl/wiki/Tutorial:_OpenGL_3.0_Context_Creation_(GLX) + +#include "render_context.hpp" +#include +#include +#include +#include + +static Display * rk_display = nullptr; +static Colormap rk_colormap = 0; +static Window rk_window = 0; +static GLXContext rk_context = nullptr; +static bool rk_error_occured = false; + +#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 +typedef GLXContext (*glXCreateContextAttribsARBProc)(Display *, GLXFBConfig, GLXContext, Bool, int const *); + +static void rk_printf( + char const * messsage) { + printf("[GLX] %s\n", messsage); +} + +static bool rk_extension_supported( + char const * extlist, + char const * extension) { + char const * where = strchr(extension, ' '); + if (where || *extension == '\0') { + return false; + } + for (char const * start = extlist;;) { + where = strstr(start, extension); + if (!where) { + break; + } + char const * const terminator = where + strlen(extension); + if ((where == start || *(where - 1) == ' ') && (*terminator == ' ' || *terminator == '\0')) { + return true; + } + start = terminator; + } + return false; +} + +static int rk_error_handler( + Display * display, + XErrorEvent * event) { + rk_error_occured = true; + return 0; +} + +rk_window_t rk_create_context( + char const * name, + int profile, + int major, + int minor) { + rk_display = XOpenDisplay(nullptr); + if (!rk_display) { + rk_printf("Failed to open X display."); + return nullptr; + } + + static int const visual_attribs[] = { + GLX_X_RENDERABLE, True, + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT, + GLX_RENDER_TYPE, GLX_RGBA_BIT, + GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR, + GLX_RED_SIZE, 8, + GLX_GREEN_SIZE, 8, + GLX_BLUE_SIZE, 8, + GLX_ALPHA_SIZE, 8, + GLX_DEPTH_SIZE, 24, + GLX_STENCIL_SIZE, 8, + GLX_DOUBLEBUFFER, True, + None + }; + + int glx_major, glx_minor; + if (!glXQueryVersion(rk_display, &glx_major, &glx_minor) || (glx_major == 1 && glx_minor < 3) || glx_major < 1) { + rk_printf("Invalid GLX version."); + rk_destroy_context(); + return nullptr; + } + + int fbcount; + GLXFBConfig * const fbc = glXChooseFBConfig(rk_display, DefaultScreen(rk_display), visual_attribs, &fbcount); + if (!fbc) { + rk_printf("Failed to retrieve a framebuffer config."); + rk_destroy_context(); + return nullptr; + } + + int best_fbc = -1; + int best_num_samp = -1; + for (int i = 0; i < fbcount; ++i) { + XVisualInfo * vi = glXGetVisualFromFBConfig(rk_display, fbc[i]); + if (vi) { + int srgb = 0; + glXGetFBConfigAttrib(rk_display, fbc[i], GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, &srgb); + if (srgb) { + int samp_buf, samples; + glXGetFBConfigAttrib(rk_display, fbc[i], GLX_SAMPLE_BUFFERS, &samp_buf); + glXGetFBConfigAttrib(rk_display, fbc[i], GLX_SAMPLES, &samples); + if (best_fbc < 0 || (samp_buf && samples > best_num_samp)) { + best_fbc = i; + best_num_samp = samples; + } + } + XFree(vi); + } + } + if (best_fbc == -1) { + XFree(fbc); + rk_printf("Failed to find sRGB framebuffer."); + rk_destroy_context(); + return nullptr; + } + GLXFBConfig const bestFbc = fbc[best_fbc]; + XFree(fbc); + + XVisualInfo * const vi = glXGetVisualFromFBConfig(rk_display, bestFbc); + rk_colormap = XCreateColormap(rk_display, RootWindow(rk_display, vi->screen), vi->visual, AllocNone); + XSetWindowAttributes swa; + swa.colormap = rk_colormap; + swa.background_pixmap = None; + swa.border_pixel = 0; + swa.event_mask = StructureNotifyMask; + rk_window = XCreateWindow(rk_display, RootWindow(rk_display, vi->screen), + 0, 0, 1600, 900, 0, vi->depth, InputOutput, vi->visual, + CWBorderPixel | CWColormap | CWEventMask, &swa); + if (!rk_window) { + rk_printf("Failed to create window."); + rk_destroy_context(); + return nullptr; + } + XFree(vi); + XStoreName(rk_display, rk_window, name); + XMapWindow( rk_display, rk_window ); + + char const * const glxExts = glXQueryExtensionsString(rk_display, DefaultScreen(rk_display)); + glXCreateContextAttribsARBProc glXCreateContextAttribsARB = 0; + glXCreateContextAttribsARB = (glXCreateContextAttribsARBProc) + glXGetProcAddressARB((const GLubyte *)("glXCreateContextAttribsARB")); + + rk_error_occured = false; + int (*oldHandler)(Display *, XErrorEvent *) = XSetErrorHandler(&rk_error_handler); + if (!rk_extension_supported(glxExts, "GLX_ARB_create_context") || !glXCreateContextAttribsARB) { + rk_printf("glXCreateContextAttribsARB() not found."); + rk_destroy_context(); + return nullptr; + } else { + const int context_attribs[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, major, + GLX_CONTEXT_MINOR_VERSION_ARB, minor, + GLX_CONTEXT_PROFILE_MASK_ARB, profile, + None + }; + rk_context = glXCreateContextAttribsARB(rk_display, bestFbc, 0, True, context_attribs); + XSync(rk_display, False); + if (rk_error_occured || !rk_context) { + rk_printf("Failed to create context."); + rk_destroy_context(); + return nullptr; + } + } + XSetErrorHandler(oldHandler); + + if (!glXIsDirect(rk_display, rk_context)) { + rk_printf("Warning: Rendering context is indirect."); + } + glXMakeCurrent(rk_display, rk_window, rk_context); + return reinterpret_cast(rk_window); +} + +void rk_swap_buffers() { + if (rk_display && rk_window) { + glXSwapBuffers(rk_display, rk_window); + } +} + +char ** rk_load_shader_source( + char const * filename, + rk_uint * length) { + char ** shader = nullptr; + char buffer[1024]; + FILE * const file = fopen(filename, "rt"); + if (file) { + int nlines = 0; + while (fgets(buffer, sizeof(buffer), file)) { + ++nlines; + } + if (nlines) { + rewind(file); + shader = new char*[nlines]; + for (int line = 0; line < nlines; ++line) { + shader[line] = new char[sizeof(buffer)]; + fgets(shader[line], sizeof(buffer), file); + } + } else { + printf("Shader %s is empty.\n", filename); + } + fclose(file); + *length = nlines; + } else { + printf("Cannot open shader %s.\n", filename); + } + return shader; +} + +void rk_free_shader_source( + char ** shader, + rk_uint length) { + if (shader) { + for (rk_uint line = 0; line < length; ++line) { + delete[] shader[line]; + } + } + delete[] shader; +} + +void rk_destroy_context() { + if (rk_display) { + glXMakeCurrent(rk_display, 0, nullptr); + if (rk_context) { + glXDestroyContext(rk_display, rk_context); + rk_context = nullptr; + } + if (rk_window) { + XDestroyWindow(rk_display, rk_window); + rk_window = 0; + } + if (rk_colormap) { + XFreeColormap(rk_display, rk_colormap); + rk_colormap = 0; + } + XCloseDisplay(rk_display); + rk_display = nullptr; + } +} diff --git a/cpp/opengl/render_opengles.cpp b/cpp/opengl/render_opengles.cpp new file mode 100644 index 0000000..23e0830 --- /dev/null +++ b/cpp/opengl/render_opengles.cpp @@ -0,0 +1,816 @@ +// Copyright (C) 2022 RozK +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "render_opengles.hpp" +#include +#include + +//TODO: move the GLX stuff to render_context_glx.cpp +#include + +static rk_shader const * rk_current_shader = nullptr; +static rk_vertices const * rk_current_vertices = nullptr; +static bool rk_frame = false; + +static PFNGLDRAWELEMENTSINSTANCEDBASEINSTANCEPROC rk_DrawElementsInstancedBaseInstance = nullptr; +static PFNGLMULTIDRAWELEMENTSINDIRECTPROC rk_MultiDrawElementsIndirect = nullptr; + +static void rk_printf(char const * messsage) { + printf("[RK_ENGINE] %s\n", messsage); +} + +#define rk_error(message) { if (glGetError() != GL_NO_ERROR) { rk_printf(message); } } + +static void rk_debug_message_callback( + GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + GLchar const * message, + void const * userParam) { + if (id == 131169 || id == 131185 || id == 131218 || id == 131204) { + return; + } + printf("[RK_ENGINE][GL] (id=%d) %s\n", id, message); +} + +static __GLXextFuncPtr rk_extension( + char const * const name, + char const * const func) { + __GLXextFuncPtr ext_ptr = nullptr; + GLint num_exts = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &num_exts); + for (int ext_index = 0; ext_index < num_exts; ++ext_index) { + char const * const ext_name = reinterpret_cast(glGetStringi(GL_EXTENSIONS, ext_index)); + if (strcmp(name, ext_name) == 0) { + ext_ptr = glXGetProcAddressARB(reinterpret_cast(func)); + printf("[RK_ENGINE] Using extension %s::%s\n", name, func); + break; + } + } + return ext_ptr; +} + +rk_window_t rk_initialize( + char const * name) { + rk_window_t const window = rk_create_context(name, GLX_CONTEXT_ES_PROFILE_BIT_EXT, 3, 2); + if (window) { + GLubyte const * const vendor = glGetString(GL_VENDOR); + GLubyte const * const renderer = glGetString(GL_RENDERER); + printf("[RK_ENGINE] vendor: %s, renderer: %s\n", vendor, renderer); + GLubyte const * const version = glGetString(GL_VERSION); + GLubyte const * const language = glGetString(GL_SHADING_LANGUAGE_VERSION); + printf("[RK_ENGINE] version: %s, language: %s\n", version, language); + + rk_DrawElementsInstancedBaseInstance = + reinterpret_cast( + rk_extension("GL_EXT_base_instance", "DrawElementsInstancedBaseInstance")); + if (rk_DrawElementsInstancedBaseInstance) { + rk_MultiDrawElementsIndirect = + reinterpret_cast( + rk_extension("GL_EXT_multi_draw_indirect", "MultiDrawElementsIndirectEXT")); + } + + GLint context_flags = 0; + glGetIntegerv(GL_CONTEXT_FLAGS, &context_flags); + if (context_flags & GL_CONTEXT_FLAG_DEBUG_BIT) { + printf("[RK_ENGINE] Debug context enabled\n"); + glDebugMessageCallback(rk_debug_message_callback, nullptr); + glEnable(GL_DEBUG_OUTPUT); + } else { + glDisable(GL_DEBUG_OUTPUT); + } + + glDisable(GL_BLEND); + glEnable(GL_DITHER); + glEnable(GL_DEPTH_TEST); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_STENCIL_TEST); + glEnable(GL_CULL_FACE); + glFrontFace(GL_CCW); + glCullFace(GL_BACK); + glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST); + } + return window; +} + +static void rk_print_shader_infolog(GLuint shader) { + int length; + char infolog[1024]; + glGetShaderInfoLog(shader, sizeof(infolog), &length, infolog); + if (length > 0) { + rk_printf(infolog); + } +} + +static void rk_print_program_infolog(GLuint program) { + int length; + char infolog[1024]; + glGetProgramInfoLog(program, sizeof(infolog), &length, infolog); + if (length > 0) { + rk_printf(infolog); + } +} + +//TODO: external loading of shader sources +//TODO: error handling +rk_shader_t rk_load_shader( + char const * name) { + rk_shader * const shader = new rk_shader; + shader->vertex = glCreateShader(GL_VERTEX_SHADER); + shader->fragment = glCreateShader(GL_FRAGMENT_SHADER); + shader->program = glCreateProgram(); + + char vertex_name[256]; + snprintf(vertex_name, sizeof(vertex_name), "%s_opengles.vert", name); + printf("[RK_ENGINE] Loading vertex shader %s...\n", vertex_name); + rk_uint vertex_length = 0; + char ** vertex_source = rk_load_shader_source(vertex_name, &vertex_length); + if (vertex_source) { + rk_printf("Compiling vertex shader..."); + glShaderSource(shader->vertex, vertex_length, vertex_source, nullptr); + } + glCompileShader(shader->vertex); + rk_error("glCompileShader() failed."); + rk_print_shader_infolog(shader->vertex); + + char fragment_name[256]; + snprintf(fragment_name, sizeof(fragment_name), "%s_opengles.frag", name); + printf("[RK_ENGINE] Loading fragment shader %s...\n", fragment_name); + rk_uint fragment_length = 0; + char ** fragment_source = rk_load_shader_source(fragment_name, &fragment_length); + if (fragment_source) { + rk_printf("Compiling fragment shader..."); + glShaderSource(shader->fragment, fragment_length, fragment_source, nullptr); + } + glCompileShader(shader->fragment); + rk_error("glCompileShader() failed."); + rk_print_shader_infolog(shader->fragment); + + rk_printf("Linking program..."); + glAttachShader(shader->program, shader->vertex); + glAttachShader(shader->program, shader->fragment); + glLinkProgram(shader->program); + rk_error("glLinkProgram() failed."); + rk_print_program_infolog(shader->program); + + rk_printf("Done."); + + glReleaseShaderCompiler(); + if (vertex_source != nullptr) { + rk_free_shader_source(vertex_source, vertex_length); + } + if (fragment_source != nullptr) { + rk_free_shader_source(fragment_source, fragment_length); + } + + shader->uniforms.view = glGetUniformLocation(shader->program, "u_view"); + shader->uniforms.view_km = glGetUniformLocation(shader->program, "u_view_km"); + shader->uniforms.projection = glGetUniformLocation(shader->program, "u_projection"); + + return shader; +} + +void rk_select_shader( + rk_shader_t _shader) { + rk_shader * const shader = reinterpret_cast(_shader); + if (shader) { + rk_current_shader = shader; + glUseProgram(shader->program); + if (rk_frame) { + if (shader->uniforms.view > -1) { + glUniformMatrix4fv(shader->uniforms.view, 1, GL_FALSE, glm::value_ptr(rk_view)); + } + if (shader->uniforms.view_km > -1) { + glUniformMatrix4fv(shader->uniforms.view_km, 1, GL_FALSE, glm::value_ptr(rk_view_km)); + } + if (shader->uniforms.projection > -1) { + glUniformMatrix4fv(shader->uniforms.projection, 1, GL_FALSE, glm::value_ptr(rk_projection)); + } + } + } +} + +rk_input_t rk_resolve_input( + char const * name) { + if (!rk_current_shader || !name) { + return nullptr; + } + GLint const uniform = glGetUniformLocation(rk_current_shader->program, name); + return reinterpret_cast(uniform + 1); +} + +void rk_set_input_float( + rk_input_t _input, + float value) { + GLint const input = reinterpret_cast(_input) - 1; + if (rk_current_shader && input > -1) { + glUniform1f(input, value); + } +} + +void rk_set_input_vec3( + rk_input_t _input, + rk_vec3 const & value, + rk_input_mode mode) { + GLint const input = reinterpret_cast(_input) - 1; + if (rk_current_shader && input > -1) { + switch (mode) { + case RK_INPUT_IDENTITY: + glUniform3fv(input, 1, glm::value_ptr(value)); + break; + case RK_INPUT_VIEW_POSITION: { + glUniform3fv(input, 1, glm::value_ptr(rk_view * rk_vec4(value, 1.0))); + break; + } + case RK_INPUT_VIEW_ORIENTATION: { + glUniform3fv(input, 1, glm::value_ptr(rk_view * rk_vec4(value, 0.0))); + break; + } + } + } +} + +rk_texture_t rk_create_texture( + rk_uint slot, + char const * input, + rk_texture_format format, + rk_uint width, + rk_uint height, + rk_uint nlevels, + rk_texture_flags flags, + void const * pixels) { + if (!input || width == 0 || height == 0 || !pixels || !rk_current_shader) { + return nullptr; + } + GLint internal_format; + GLenum source_format; + GLenum source_type; + switch (format) { + case RK_TEXTURE_FORMAT_SRGB8_A8: + internal_format = GL_SRGB8_ALPHA8; + source_format = GL_RGBA; + source_type = GL_UNSIGNED_BYTE; + break; + case RK_TEXTURE_FORMAT_RGBA8: + internal_format = GL_RGBA8; + source_format = GL_RGBA; + source_type = GL_UNSIGNED_BYTE; + break; + case RK_TEXTURE_FORMAT_RGB10_A2: + internal_format = GL_RGB10_A2; + source_format = GL_RGBA; + source_type = GL_UNSIGNED_INT_2_10_10_10_REV; + break; + case RK_TEXTURE_FORMAT_32F: + internal_format = GL_R32F; + source_format = GL_RED; + source_type = GL_FLOAT; + break; + default: + return nullptr; + break; + } + rk_texture * const texture = new rk_texture; + glActiveTexture(GL_TEXTURE0 + slot); + glGenTextures(1, &texture->texture); + GLenum target; + if (nlevels) { + if (flags & RK_TEXTURE_FLAG_3D) { + target = GL_TEXTURE_3D; + } else { + target = GL_TEXTURE_2D_ARRAY; + } + glBindTexture(target, texture->texture); + //TODO: glTexStorage3D + glTexImage3D(target, 0, internal_format, width, height, nlevels, 0, source_format, source_type, pixels); + } else { + target = GL_TEXTURE_2D; + glBindTexture(target, texture->texture); + //TODO: glTexStorage2D + glTexImage2D(target, 0, internal_format, width, height, 0, source_format, source_type, pixels); + } + if (flags & RK_TEXTURE_FLAG_MIPMAPS) { + if (flags & RK_TEXTURE_FLAG_MIN_LINEAR) { + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } else { + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); + } + } else { + if (flags & RK_TEXTURE_FLAG_MIN_LINEAR) { + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } else { + glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + } + if (flags & RK_TEXTURE_FLAG_MAG_LINEAR) { + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_REPEAT); + if (flags & RK_TEXTURE_FLAG_MIPMAPS) { + glGenerateMipmap(target); + } + texture->slot = slot; + texture->nlevels = nlevels; + texture->sampler = glGetUniformLocation(rk_current_shader->program, input); + if (texture->sampler == -1) { + printf("[RK_ENGINE] glGetUniformLocation(%s) failed\n", input); + } + glBindTexture(target, 0); + return texture; +} + +rk_triangles_t rk_create_triangles( + rk_uint nvertices, + rk_vec3 const * vertices) { + if (nvertices == 0 || !vertices || !rk_current_shader) { + return nullptr; + } + rk_triangles * const triangles = new rk_triangles; + triangles->size = nvertices; + glGenVertexArrays(1, &triangles->array); + glBindVertexArray(triangles->array); + glGenBuffers(1, &triangles->vertices); + glBindBuffer(GL_ARRAY_BUFFER, triangles->vertices); + glBufferData(GL_ARRAY_BUFFER, nvertices * sizeof(rk_vec3), vertices, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + return triangles; +} + +rk_vertices_t rk_create_vertices( + rk_vertex_format const * format, + rk_uint nvertices, + void const * _vertices, + rk_uint nindices, + rk_ushort const * indices) { + if (!format || !nvertices || !_vertices || !nindices || !indices || !rk_current_shader) { + return nullptr; + } + rk_uint vertex_size = 0; + for (rk_vertex_format const * f = format; *f; ++f) { + switch (*f) { + case RK_VERTEX_FORMAT_VEC2_FLOAT: + vertex_size += sizeof(float) * 2; + break; + case RK_VERTEX_FORMAT_VEC2_USHORT: + vertex_size += sizeof(rk_ushort) * 2; + break; + case RK_VERTEX_FORMAT_VEC3_FLOAT: + vertex_size += sizeof(float) * 3; + break; + case RK_VERTEX_FORMAT_VEC3_INT10: + vertex_size += sizeof(rk_uint); + break; + default: + rk_printf("rk_create_vertices(): invalid format."); + return nullptr; + break; + } + } + if (!vertex_size) { + rk_printf("rk_create_vertices(): empty format."); + return nullptr; + } + rk_vertices * const vertices = new rk_vertices; + glGenVertexArrays(1, &vertices->array); + glBindVertexArray(vertices->array); + glGenBuffers(1, &vertices->vertices); + glBindBuffer(GL_ARRAY_BUFFER, vertices->vertices); + glBufferData(GL_ARRAY_BUFFER, nvertices * vertex_size, _vertices, GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + vertices->vertex_size = vertex_size; + vertices->layout = 0; + rk_uint offset = 0; + for (rk_vertex_format const * f = format; *f; ++f, ++vertices->layout) { + glEnableVertexAttribArray(vertices->layout); + switch (*f) { + case RK_VERTEX_FORMAT_VEC2_FLOAT: + glVertexAttribFormat(vertices->layout, 2, GL_FLOAT, GL_FALSE, offset); + glVertexAttribBinding(vertices->layout, RK_VERTICES_BINDING); + offset += sizeof(float) * 2; + break; + case RK_VERTEX_FORMAT_VEC2_USHORT: + glVertexAttribFormat(vertices->layout, 2, GL_UNSIGNED_SHORT, GL_TRUE, offset); + glVertexAttribBinding(vertices->layout, RK_VERTICES_BINDING); + offset += sizeof(rk_ushort) * 2; + break; + case RK_VERTEX_FORMAT_VEC3_FLOAT: + glVertexAttribFormat(vertices->layout, 3, GL_FLOAT, GL_FALSE, offset); + glVertexAttribBinding(vertices->layout, RK_VERTICES_BINDING); + offset += sizeof(float) * 3; + break; + case RK_VERTEX_FORMAT_VEC3_INT10: + glVertexAttribFormat(vertices->layout, GL_BGRA, GL_INT_2_10_10_10_REV, GL_TRUE, offset); + glVertexAttribBinding(vertices->layout, RK_VERTICES_BINDING); + offset += sizeof(rk_uint); + break; + default: + break; + } + } + glBindVertexBuffer(RK_VERTICES_BINDING, vertices->vertices, 0, vertices->vertex_size); + glGenBuffers(1, &vertices->indices); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vertices->indices); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, nindices * sizeof(rk_ushort), indices, GL_STATIC_DRAW); + glBindVertexArray(0); + return vertices; +} + +//TODO: support for mat3 orientations with packing into int10 * 3 +// - maybe from quaternions inputs +// - maybe it's possible to implement efficient quaternions in glsl? + +rk_batch_t rk_create_batch( + rk_uint max_size, + rk_batch_translation_format translation_format, + rk_batch_orientation_format orientation_format) { + if (!max_size || max_size > RK_BATCH_MAX_SIZE || !rk_current_shader || !rk_current_vertices) { + return nullptr; + } + rk_uint translation_size = 0; + switch (translation_format) { + case RK_BATCH_TRANSLATION_FORMAT_FLOAT: + translation_size = sizeof(float) * 4; + break; + case RK_BATCH_TRANSLATION_FORMAT_SHORT: + translation_size = sizeof(rk_short) * 4; + break; + default: + rk_printf("rk_create_batch(): invalid translation format."); + return nullptr; + break; + } + rk_uint orientation_size = 0; + switch (orientation_format) { + case RK_BATCH_ORIENTATION_FORMAT_NONE: + orientation_size = 0; + break; + case RK_BATCH_ORIENTATION_FORMAT_FLOAT: + orientation_size = sizeof(float) * 3; + break; + case RK_BATCH_ORIENTATION_FORMAT_INT10: + orientation_size = sizeof(rk_uint); + break; + default: + rk_printf("rk_create_batch(): invalid orientation format."); + return nullptr; + break; + } + rk_uint const params_size = translation_size + orientation_size; + rk_batch * batch = new rk_batch; + batch->size = max_size; + batch->translation_format = translation_format; + batch->orientation_format = orientation_format; + batch->params_size = params_size; + batch->indices = new rk_ushort[batch->size]; + batch->params = new rk_ubyte[batch->size * params_size]; + batch->commands = new rk_command[batch->size * sizeof(rk_command)]; + glGenBuffers(1, &batch->params_buffer); + rk_uint const translation_layout = rk_current_vertices->layout; + glEnableVertexAttribArray(translation_layout); + switch (translation_format) { + case RK_BATCH_TRANSLATION_FORMAT_FLOAT: + glVertexAttribFormat(translation_layout, 4, GL_FLOAT, GL_FALSE, 0); + break; + case RK_BATCH_TRANSLATION_FORMAT_SHORT: + glVertexAttribFormat(translation_layout, 4, GL_SHORT, GL_FALSE, 0); + break; + } + glVertexAttribBinding(translation_layout, RK_PARAMS_BINDING); + rk_uint const orientation_layout = rk_current_vertices->layout + 1; + switch (orientation_format) { + case RK_BATCH_ORIENTATION_FORMAT_NONE: + break; + case RK_BATCH_ORIENTATION_FORMAT_FLOAT: + glEnableVertexAttribArray(orientation_layout); + glVertexAttribFormat(orientation_layout, 3, GL_FLOAT, GL_FALSE, translation_size); + glVertexAttribBinding(orientation_layout, RK_PARAMS_BINDING); + break; + case RK_BATCH_ORIENTATION_FORMAT_INT10: + glEnableVertexAttribArray(orientation_layout); + glVertexAttribFormat(orientation_layout, GL_BGRA, GL_INT_2_10_10_10_REV, GL_TRUE, translation_size); + glVertexAttribBinding(orientation_layout, RK_PARAMS_BINDING); + break; + } + glVertexBindingDivisor(RK_PARAMS_BINDING, 1); + glBindVertexBuffer(RK_PARAMS_BINDING, batch->params_buffer, 0, batch->params_size); + if (rk_MultiDrawElementsIndirect) { + glGenBuffers(1, &batch->commands_buffer); + } + return batch; +} + +void rk_begin_frame() { + rk_frame = true; + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); +} + +void rk_select_texture( + rk_texture_t _texture) { + rk_texture const * const texture = reinterpret_cast(_texture); + if (texture) { + glActiveTexture(GL_TEXTURE0 + texture->slot); + if (texture->nlevels) { + glBindTexture(GL_TEXTURE_2D_ARRAY, texture->texture); + } else { + glBindTexture(GL_TEXTURE_2D, texture->texture); + } + if (texture->sampler > -1) { + glUniform1i(texture->sampler, texture->slot); + } + } +} + +RK_EXPORT void rk_draw_triangles( + rk_triangles_t _triangles) { + rk_triangles const * const triangles = reinterpret_cast(_triangles); + if (triangles && rk_current_shader && !rk_current_vertices) { + glBindVertexArray(triangles->array); + glDrawArrays(GL_TRIANGLES, 0, triangles->size); + glBindVertexArray(0); + } +} + +void rk_select_vertices( + rk_vertices_t _vertices) { + rk_vertices * const vertices = reinterpret_cast(_vertices); + if (vertices && rk_current_shader) { + glBindVertexArray(vertices->array); + rk_current_vertices = vertices; + } +} + +static rk_uint rk_batch_filter( + rk_uint const size, + rk_ushort * const _indices, + rk_instance_flags const * flags) { + rk_ushort * indices = _indices; + for (rk_ushort index = 0; index < size; ++index, ++flags) { + if ((*flags & RK_INSTANCE_FLAGS_SPAWNED_VISIBLE) == RK_INSTANCE_FLAGS_SPAWNED_VISIBLE) { + *indices++ = index; + } + } + return indices - _indices; +} + +static rk_uint rk_batch_build_commands( + rk_uint const count, + rk_ushort * const indices, + rk_command * const _commands, + rk_mesh const * const meshes) { + rk_command * commands = _commands; + rk_ushort * base = indices; + rk_ushort * const last = indices + count; + for (rk_ushort * first = indices; first < last; base = first, ++commands) { + rk_mesh const & mesh = meshes[*first++]; + for ( ; first < last && meshes[*first].packed == mesh.packed; ++first) { + } + for (rk_ushort * second = first; second < last; ++second) { + rk_ushort const index = *second; + if (meshes[index].packed == mesh.packed) { + *second = *first; + *first++ = index; + } + } + commands->count = static_cast(mesh.count) * 3; + commands->ninstances = first - base; + commands->base_index = mesh.offset; + commands->base_vertex = 0; + commands->base_instance = base - indices; + } + return commands - _commands; +} + +template < typename _instance_params_t > +static void rk_batch_convert_params( + rk_uint const count, + rk_ushort const * const _indices, + rk_ubyte * const _params, + rk_vec3 const * const translations, + rk_ushort const * const texlevels, + rk_vec3 const * const orientations) { + _instance_params_t * params = reinterpret_cast<_instance_params_t *>(_params); + rk_ushort const * const last = _indices + count; + for (rk_ushort const * indices = _indices; indices < last; ++indices, ++params) { + rk_uint const index = *indices; + params->set(translations[index], texlevels[index], orientations[index]); + } +} + +template < typename _instance_params_t > +static void rk_batch_convert_params( + rk_uint const count, + rk_ushort const * const _indices, + rk_ubyte * const _params, + rk_vec3 const * const translations, + rk_ushort const * const texlevels) { + _instance_params_t * params = reinterpret_cast<_instance_params_t *>(_params); + rk_ushort const * const last = _indices + count; + for (rk_ushort const * indices = _indices; indices < last; ++indices, ++params) { + rk_uint const index = *indices; + params->set(translations[index], texlevels[index]); + } +} + +void rk_draw_batch( + rk_batch_t _batch, + rk_uint size, + rk_instance_flags const * flags, + rk_ushort const * texlevels, + rk_mesh const * meshes, + rk_vec3 const * translations, + rk_vec3 const * orientations) { + rk_batch & batch = *reinterpret_cast(_batch); + if (!size || size > batch.size || !flags || !texlevels || !meshes || !translations || + !rk_current_shader || !rk_current_vertices) { + return; + } + if (batch.orientation_format != RK_BATCH_ORIENTATION_FORMAT_NONE && !orientations) { + return; + } + rk_uint const count = rk_batch_filter(size, batch.indices, flags); + if (!count) { + return; + } + rk_uint const ncommands = rk_batch_build_commands(count, batch.indices, batch.commands, meshes); + if (rk_MultiDrawElementsIndirect) { + glBindBuffer(GL_DRAW_INDIRECT_BUFFER, batch.commands_buffer); + glBufferData(GL_DRAW_INDIRECT_BUFFER, ncommands * sizeof(rk_command), batch.commands, GL_STREAM_DRAW); + } + switch (batch.translation_format) { + case RK_BATCH_TRANSLATION_FORMAT_FLOAT: + switch (batch.orientation_format) { + case RK_BATCH_ORIENTATION_FORMAT_NONE: + rk_batch_convert_params( + count, batch.indices, batch.params, translations, texlevels); + break; + case RK_BATCH_ORIENTATION_FORMAT_FLOAT: + rk_batch_convert_params( + count, batch.indices, batch.params, translations, texlevels, orientations); + break; + case RK_BATCH_ORIENTATION_FORMAT_INT10: + rk_batch_convert_params( + count, batch.indices, batch.params, translations, texlevels, orientations); + break; + } + break; + case RK_BATCH_TRANSLATION_FORMAT_SHORT: + switch (batch.orientation_format) { + case RK_BATCH_ORIENTATION_FORMAT_NONE: + rk_batch_convert_params( + count, batch.indices, batch.params, translations, texlevels); + break; + case RK_BATCH_ORIENTATION_FORMAT_FLOAT: + rk_batch_convert_params( + count, batch.indices, batch.params, translations, texlevels, orientations); + break; + case RK_BATCH_ORIENTATION_FORMAT_INT10: + rk_batch_convert_params( + count, batch.indices, batch.params, translations, texlevels, orientations); + break; + } + break; + } + glBindBuffer(GL_ARRAY_BUFFER, batch.params_buffer); + glBufferData(GL_ARRAY_BUFFER, count * batch.params_size, batch.params, GL_STREAM_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + if (batch.orientation_format == RK_BATCH_ORIENTATION_FORMAT_NONE) { + rk_vec3 const forward(0.f, 1.f, 0.f); + glVertexAttrib3fv(rk_current_vertices->layout + 1, glm::value_ptr(forward)); + } + if (rk_DrawElementsInstancedBaseInstance) { + if (rk_MultiDrawElementsIndirect) { + rk_MultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_SHORT, nullptr, ncommands, sizeof(rk_command)); + glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0); + } else { + rk_command const * const last_command = batch.commands + ncommands; + for (rk_command const * command = batch.commands; command < last_command; ++command) { + rk_DrawElementsInstancedBaseInstance( + GL_TRIANGLES, command->count, GL_UNSIGNED_SHORT, + reinterpret_cast(command->base_index << 1), + command->ninstances, command->base_instance); + } + } + } else { + rk_uint params_offset = 0; + rk_command const * const last_command = batch.commands + ncommands; + for (rk_command const * command = batch.commands; command < last_command; ++command) { + glBindVertexBuffer(RK_PARAMS_BINDING, batch.params_buffer, params_offset, batch.params_size); + params_offset += command->ninstances * batch.params_size; + glDrawElementsInstanced( + GL_TRIANGLES, command->count, GL_UNSIGNED_SHORT, + reinterpret_cast(command->base_index << 1), + command->ninstances); + } + } +} + +void rk_unselect_vertices( + rk_vertices_t _vertices) { + rk_current_vertices = nullptr; + glBindVertexArray(0); +} + +void rk_unselect_texture( + rk_texture_t _texture) { + rk_texture const * const texture = reinterpret_cast(_texture); + if (texture) { + glActiveTexture(GL_TEXTURE0 + texture->slot); + if (texture->nlevels) { + glBindTexture(GL_TEXTURE_2D, 0); + } else { + glBindTexture(GL_TEXTURE_2D_ARRAY, 0); + } + } +} + +void rk_unselect_shader( + rk_shader_t _shader) { + rk_current_shader = nullptr; + glUseProgram(0); +} + +void rk_end_frame() { + rk_swap_buffers(); + rk_frame = false; +} + +void rk_destroy_batch( + rk_batch_t _batch) { + rk_batch * const batch = reinterpret_cast(_batch); + if (batch) { + delete[] batch->indices; + delete[] batch->params; + delete[] batch->commands; + glDeleteBuffers(1, &batch->params_buffer); + if (rk_MultiDrawElementsIndirect) { + glDeleteBuffers(1, &batch->commands_buffer); + } + delete batch; + } +} + +void rk_destroy_triangles( + rk_triangles_t _triangles) { + rk_triangles * const triangles = reinterpret_cast(_triangles); + if (triangles) { + glDeleteBuffers(1, &triangles->vertices); + glDeleteVertexArrays(1, &triangles->array); + delete triangles; + } +} + +void rk_destroy_vertices( + rk_vertices_t _vertices) { + rk_vertices * const vertices = reinterpret_cast(_vertices); + if (vertices) { + glDeleteBuffers(1, &vertices->indices); + glDeleteBuffers(1, &vertices->vertices); + glDeleteVertexArrays(1, &vertices->array); + delete vertices; + } +} + +void rk_destroy_texture( + rk_texture_t _texture) { + rk_texture * const texture = reinterpret_cast(_texture); + if (texture) { + glDeleteTextures(1, &texture->texture); + delete texture; + } +} + +void rk_destroy_shader( + rk_shader_t _shader) { + rk_shader * const shader = reinterpret_cast(_shader); + if (shader) { + glDeleteShader(shader->vertex); + glDeleteShader(shader->fragment); + glDeleteProgram(shader->program); + delete shader; + } +} + +void rk_terminate() { + rk_destroy_context(); +} diff --git a/cpp/opengl/render_opengles.hpp b/cpp/opengl/render_opengles.hpp new file mode 100644 index 0000000..ee48709 --- /dev/null +++ b/cpp/opengl/render_opengles.hpp @@ -0,0 +1,143 @@ +// Copyright (C) 2022 RozK +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef _RK_ENGINE_RENDER_OPENGLES_H +#define _RK_ENGINE_RENDER_OPENGLES_H + +#include "../render.hpp" +#include "render_context.hpp" +#include +#include +#include + +enum : rk_uint { + RK_VERTICES_BINDING = 0, + RK_PARAMS_BINDING = 1 +}; + +struct rk_uniforms { + GLint view; + GLint view_km; + GLint projection; +}; + +struct rk_shader { + GLuint vertex; + GLuint fragment; + GLuint program; + rk_uniforms uniforms; +}; + +struct rk_texture { + rk_uint slot; + rk_uint nlevels; + GLint sampler; + GLuint texture; +}; + +struct rk_triangles { + rk_uint size; + GLuint array; + GLuint vertices; +}; + +struct rk_vertices { + rk_uint vertex_size; + rk_uint layout; + GLuint array; + GLuint vertices; + GLuint indices; +}; + +struct rk_command { + GLuint count; + GLuint ninstances; + GLuint base_index; + GLint base_vertex; + GLuint base_instance; +}; + +struct rk_batch { + rk_uint size; + rk_batch_translation_format translation_format; + rk_batch_orientation_format orientation_format; + rk_uint params_size; + rk_ushort * indices; + rk_ubyte * params; + rk_command * commands; + GLuint params_buffer; + GLuint commands_buffer; +}; + +struct rk_translation_float { + rk_vec3 xyz; + float l; + + inline void set(rk_vec3 const & translation, rk_ushort const texlevel) { + xyz = translation; + l = static_cast(texlevel); + } +}; + +struct rk_translation_short { + rk_short x; + rk_short y; + rk_short z; + rk_ushort l; + + inline void set(rk_vec3 const & translation, rk_ushort const texlevel) { + x = static_cast(translation.x); + y = static_cast(translation.y); + z = static_cast(translation.z); + l = texlevel; + } +}; + +struct rk_orientation_float { + rk_vec3 xyz; + + inline void set(rk_vec3 const & orientation) { + xyz = orientation; + } +}; + + +struct rk_orientation_int10 { + rk_uint xyz; + + inline void set(rk_vec3 const & orientation) { + #define _pack_10(x) static_cast(static_cast((x) * ((x) < 0.f ? 512.f : 511.f)) & 1023) + xyz = _pack_10(orientation.x) << 20 | _pack_10(orientation.y) << 10 | _pack_10(orientation.z); + #undef _pack_10 + } +}; + +template < typename _translation_t, typename _orientation_t > +struct rk_params { + _translation_t translation; + _orientation_t orientation; + + inline void set(rk_vec3 const & translation, rk_ushort const texlevel, rk_vec3 const & orientation) { + this->translation.set(translation, texlevel); + this->orientation.set(orientation); + } +}; + +typedef rk_params rk_params_float_float; +typedef rk_params rk_params_float_int10; +typedef rk_params rk_params_short_float; +typedef rk_params rk_params_short_int10; + +#endif // _RK_ENGINE_RENDER_OPENGLES_H diff --git a/cpp/render.cpp b/cpp/render.cpp new file mode 100644 index 0000000..21a44f5 --- /dev/null +++ b/cpp/render.cpp @@ -0,0 +1,48 @@ +// Copyright (C) 2022 RozK +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include "render.hpp" + +rk_vec3 rk_view_origin; +rk_vec3 rk_view_lookat; +rk_mat4 rk_view; +rk_mat4 rk_view_km; + +float rk_projection_hfov; +float rk_projection_ratio; +float rk_projection_near; +float rk_projection_far; +rk_mat4 rk_projection; + +void rk_set_projection( + float hfov, + float ratio, + float near, + float far) { + rk_projection_hfov = hfov; + rk_projection_ratio = ratio; + rk_projection_near = near; + rk_projection_far = far; + rk_projection = glm::perspectiveRH(hfov, ratio, near, far); +} + +extern void rk_set_view( + rk_vec3 const & position, + rk_vec3 const & lookat) { + rk_view_origin = position; + rk_view_lookat = lookat; + rk_view = glm::lookAtRH(position, lookat, glm::vec3(0.0f, 0.0f, 1.0f)); + rk_view_km = glm::lookAtRH(position * 0.001f, lookat * 0.001f, glm::vec3(0.0f, 0.0f, 1.0f)); +} diff --git a/cpp/render.hpp b/cpp/render.hpp new file mode 100644 index 0000000..6f22d48 --- /dev/null +++ b/cpp/render.hpp @@ -0,0 +1,208 @@ +// Copyright (C) 2022 RozK +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#ifndef _RK_ENGINE_RENDER_H +#define _RK_ENGINE_RENDER_H + +#include "types.hpp" +#include "math.hpp" + +extern rk_vec3 rk_view_origin; +extern rk_vec3 rk_view_lookat; +extern rk_mat4 rk_view; +extern rk_mat4 rk_view_km; //TODO: remove from engine + +extern float rk_projection_hfov; +extern float rk_projection_ratio; +extern float rk_projection_near; +extern float rk_projection_far; +extern rk_mat4 rk_projection; + +typedef rk_handle_t rk_window_t; +typedef rk_handle_t rk_shader_t; +typedef rk_handle_t rk_input_t; +typedef rk_handle_t rk_texture_t; +typedef rk_handle_t rk_triangles_t; +typedef rk_handle_t rk_vertices_t; +typedef rk_handle_t rk_batch_t; + +#define RK_FLAG(bit) (1 << (bit)) + +enum rk_input_mode : rk_uint { + RK_INPUT_IDENTITY = 0, + RK_INPUT_VIEW_POSITION = 1, + RK_INPUT_VIEW_ORIENTATION = 2 +}; + +enum rk_texture_format : rk_uint { + RK_TEXTURE_FORMAT_SRGB8_A8 = 0, + RK_TEXTURE_FORMAT_RGBA8 = 1, + RK_TEXTURE_FORMAT_RGB10_A2 = 2, + RK_TEXTURE_FORMAT_32F = 3 +}; + +enum rk_texture_flags : rk_uint { + RK_TEXTURE_FLAG_3D = RK_FLAG(0), + RK_TEXTURE_FLAG_MIPMAPS = RK_FLAG(1), + RK_TEXTURE_FLAG_MIN_NEAREST = 0, + RK_TEXTURE_FLAG_MIN_LINEAR = RK_FLAG(2), + RK_TEXTURE_FLAG_MAG_NEAREST = 0, + RK_TEXTURE_FLAG_MAG_LINEAR = RK_FLAG(3), +}; + +enum rk_vertex_format : rk_ubyte { + RK_VERTEX_FORMAT_END = 0, + RK_VERTEX_FORMAT_VEC2_FLOAT = 1, + RK_VERTEX_FORMAT_VEC2_USHORT = 2, + RK_VERTEX_FORMAT_VEC3_FLOAT = 3, + RK_VERTEX_FORMAT_VEC3_INT10 = 4, +}; + +enum rk_instance_flags : rk_ubyte { + RK_INSTANCE_FLAG_SPAWNED = RK_FLAG(0), + RK_INSTANCE_FLAG_VISIBLE = RK_FLAG(1) +}; + +enum : rk_ubyte { RK_INSTANCE_FLAGS_SPAWNED_VISIBLE = RK_INSTANCE_FLAG_SPAWNED | RK_INSTANCE_FLAG_VISIBLE }; + +enum : rk_uint { RK_BATCH_MAX_SIZE = 65536 }; + +enum rk_batch_translation_format : rk_uint { + RK_BATCH_TRANSLATION_FORMAT_FLOAT = 0, + RK_BATCH_TRANSLATION_FORMAT_SHORT = 1 +}; + +enum rk_batch_orientation_format : rk_uint { + RK_BATCH_ORIENTATION_FORMAT_NONE = 0, + RK_BATCH_ORIENTATION_FORMAT_FLOAT = 1, + RK_BATCH_ORIENTATION_FORMAT_INT10 = 2 +}; + +union rk_mesh { + rk_uint packed; + struct { + rk_ushort offset; + rk_ushort count; + }; +}; + +RK_EXPORT rk_window_t rk_initialize( + char const * name); + +RK_EXPORT rk_shader_t rk_load_shader( + char const * name); + +RK_EXPORT void rk_select_shader( + rk_shader_t _shader); + +RK_EXPORT rk_input_t rk_resolve_input( + char const * name); + +RK_EXPORT void rk_set_input_float( + rk_input_t input, + float value); + +RK_EXPORT void rk_set_input_vec3( + rk_input_t input, + rk_vec3 const & value, + rk_input_mode mode); + +RK_EXPORT rk_texture_t rk_create_texture( + rk_uint slot, + char const * input, + rk_texture_format format, + rk_uint width, + rk_uint height, + rk_uint nlevels, + rk_texture_flags flags, + void const * pixels); + +RK_EXPORT rk_triangles_t rk_create_triangles( + rk_uint nvertices, + rk_vec3 const * vertices); + +RK_EXPORT rk_vertices_t rk_create_vertices( + rk_vertex_format const * format, + rk_uint nvertices, + void const * vertices, + rk_uint nindices, + rk_ushort const * indices); + +RK_EXPORT rk_batch_t rk_create_batch( + rk_uint max_size, + rk_batch_translation_format translation_format, + rk_batch_orientation_format orientation_format); + +//TODO: export math function and take a projection matrix instead of its parameters +RK_EXPORT void rk_set_projection( + float hfov, + float ratio, + float near, + float far); + +//TODO: export math function and take a view matrix instead of its parameters +RK_EXPORT void rk_set_view( + rk_vec3 const & position, + rk_vec3 const & lookat); + +RK_EXPORT void rk_begin_frame(); + +RK_EXPORT void rk_select_texture( + rk_texture_t texture); + +RK_EXPORT void rk_draw_triangles( + rk_triangles_t triangles); + +RK_EXPORT void rk_select_vertices( + rk_vertices_t vertices); + +RK_EXPORT void rk_draw_batch( + rk_batch_t batch, + rk_uint size, + rk_instance_flags const * flags, + rk_ushort const * texlevels, + rk_mesh const * meshes, + rk_vec3 const * translations, + rk_vec3 const * orientations); + +RK_EXPORT void rk_unselect_vertices( + rk_vertices_t vertices); + +RK_EXPORT void rk_unselect_texture( + rk_texture_t texture); + +RK_EXPORT void rk_unselect_shader( + rk_shader_t shader); + +RK_EXPORT void rk_end_frame(); + +RK_EXPORT void rk_destroy_batch( + rk_batch_t batch); + +RK_EXPORT void rk_destroy_triangles( + rk_triangles_t triangles); + +RK_EXPORT void rk_destroy_vertices( + rk_vertices_t vertices); + +RK_EXPORT void rk_destroy_texture( + rk_texture_t texture); + +RK_EXPORT void rk_destroy_shader( + rk_shader_t shader); + +RK_EXPORT void rk_terminate(); + +#endif // _RK_ENGINE_RENDER_H diff --git a/cpp/types.hpp b/cpp/types.hpp new file mode 100644 index 0000000..ad9e82b --- /dev/null +++ b/cpp/types.hpp @@ -0,0 +1,36 @@ +// Copyright (C) 2022 RozK +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//TODO: use glm types + +#ifndef _RK_ENGINE_TYPES_H +#define _RK_ENGINE_TYPES_H + +#include +#include +#include + +#define RK_EXPORT extern "C" + +typedef void * rk_handle_t; +typedef uint8_t rk_ubyte; +typedef int16_t rk_short; +typedef uint16_t rk_ushort; +typedef int32_t rk_int; +typedef uint32_t rk_uint; +typedef int64_t rk_long; +typedef uint64_t rk_ulong; + +#endif // _RK_ENGINE_TYPES_H diff --git a/docs/batch.odg b/docs/batch.odg new file mode 100644 index 0000000..ca56d79 Binary files /dev/null and b/docs/batch.odg differ diff --git a/docs/batch.pdf b/docs/batch.pdf new file mode 100644 index 0000000..3ff5379 Binary files /dev/null and b/docs/batch.pdf differ diff --git a/docs/batch.png b/docs/batch.png new file mode 100644 index 0000000..fd9773a Binary files /dev/null and b/docs/batch.png differ