├── .editorconfig ├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md ├── examples ├── 01-triangle.c ├── 01-triangle.png ├── 02-sky.c ├── 02-sky.glsl ├── 02-sky.png ├── 08-water-sky.glsl ├── 08-water.c ├── 08-water.glsl ├── 08-water.png ├── demo.c └── demo_util.h ├── tfx.zig ├── tinyfx.c ├── tinyfx.h └── tinyfx.hpp /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.c] 8 | indent_style = tab 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | tinyfx 3 | .vscode/ 4 | .vs/ 5 | .svn/ 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Colby Klein 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OUTPUT =tinyfx 2 | # shut up stb.h warnings that show up when we are using -Wall... 3 | SHUTUP =-Wno-pointer-to-int-cast -Wno-unused-function -Wno-unused-but-set-variable -Wno-unused-value 4 | CFLAGS =-fPIC -Wall -Wno-deprecated-declarations -ftree-vectorize -pipe -Wno-psabi $(SHUTUP) -I. -ggdb 5 | LDFLAGS = -pthread -lrt -ldl -lm 6 | DEMO = examples/demo.c 7 | SOURCES = tinyfx.c $(DEMO) 8 | OBJECTS = $(SOURCES:.c=.o) 9 | 10 | # yes, make, use my damn cores. 11 | CORES = $(shell getconf _NPROCESSORS_ONLN) 12 | MAKEFLAGS := -j $(CORES) 13 | 14 | ifneq ("$(wildcard /opt/vc/include/bcm_host.h)", "") 15 | RPI := 1 16 | endif 17 | 18 | ifeq ($(DEBUG), 1) 19 | CFLAGS += -g -DDEBUG 20 | endif 21 | 22 | ifeq ($(RPI), 1) 23 | CFLAGS += -DRPI=1 -I/opt/vc/include/ -I/opt/vc/include/interface/vcos/pthreads -I/opt/vc/include/interface/vmcs_host/linux 24 | LDFLAGS += -L/opt/vc/lib -lEGL -lbcm_host -lvcos -lvchiq_arm -lSDL2 25 | else 26 | LDFLAGS += -lSDL2 27 | endif 28 | 29 | all: $(OBJECTS) 30 | $(CC) $(OBJECTS) -o $(OUTPUT) -Wl,--whole-archive $(LDFLAGS) -Wl,--no-whole-archive -rdynamic 31 | 32 | %.o: %.c 33 | $(CC) -c $(CFLAGS) $< -o $@ 34 | 35 | run: all 36 | ./$(OUTPUT) 37 | 38 | rebuild: clean all 39 | 40 | clean: 41 | rm -f $(OUTPUT) $(OBJECTS) 42 | 43 | release: all 44 | strip -p $(OUTPUT) 45 | 46 | .PHONY: clean all release 47 | .NOTPARALLEL: clean 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinyFX 2 | Small OpenGL ES3.1+ renderer inspired by [BGFX](https://github.com/bkaradzic/bgfx). Currently master does not work on GLES2, use `gles2` branch instead. This should be fixed in the future. 3 | 4 | ## Features 5 | - Reorders draw calls to minimize state changes and avoid overdraw (in progress) 6 | - Deals with the dirty details of the graphics API for you 7 | - Bring-your-own-framework style renderer. Doesn't tell you how to architect your program 8 | - Tracks and resets state for you between draws 9 | - Out-of-order submission to views (i.e. render passes) 10 | - Uniforms separate from shader objects, all shader programs with matching uniforms are updated automatically 11 | - Compute shaders 12 | - OpenGL ES 3.1+ (ES2 supported in `gles2` branch) 13 | - OpenGL 4.3+ core (as low as 3.1 should work, but isn't regularly tested) 14 | - Supports stereo rendering for VR (integration is up to you, but the tools are there!) 15 | 16 | ## FAQ 17 | ### *Who is this for?* 18 | Anyone sick of remembering when you need barriers, implementing a state tracker for the 50th time, integrating bigger deps than your entire codebase or who just wants something less of a pain to use than OpenGL is. 19 | 20 | ### *Why not use BGFX?* 21 | BGFX is excellent, but we have different priorities and scope. 22 | 23 | ## Using TinyFX 24 | 1. Include `tinyfx.c` in your build 25 | 2. Add the location of `tinyfx.h` (and `tinyfx.hpp` if you use the C++ API) to your include paths 26 | 3. `#include ` or `#include ` and start using tfx after creating an OpenGL context (GLFW and SDL are good for this!). Remember to call `tfx_set_platform_data` with your target GL version first! 27 | 28 | ## Examples 29 | 30 | Hello Triangle (`examples/01-triangle.c`) 31 | 32 | ![](https://github.com/shakesoda/tinyfx/raw/master/examples/01-triangle.png) 33 | 34 | Sky + Camera (`examples/02-sky.c`) 35 | 36 | ![](https://github.com/shakesoda/tinyfx/raw/master/examples/02-sky.png) 37 | 38 | Water 39 | 40 | ![](https://github.com/shakesoda/tinyfx/raw/master/examples/08-water.png) 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /examples/01-triangle.c: -------------------------------------------------------------------------------- 1 | #include "tinyfx.h" 2 | #include "demo_util.h" 3 | 4 | struct { 5 | tfx_buffer vbo; 6 | tfx_program prog; 7 | } tri_res; 8 | 9 | void triangle_init(uint16_t w, uint16_t h) { 10 | const uint8_t back = 1; 11 | tfx_view_set_clear_color(back, 0x555555ff); 12 | tfx_view_set_clear_depth(back, 1.0); 13 | tfx_view_set_depth_test(back, TFX_DEPTH_TEST_LT); 14 | tfx_view_set_name(back, "Forward Pass"); 15 | 16 | const char *vss = "" 17 | "in vec3 a_position;\n" 18 | "in vec4 a_color;\n" 19 | "out vec4 v_col;\n" 20 | "void main() {\n" 21 | " v_col = a_color;\n" 22 | " gl_Position = vec4(a_position.xyz, 1.0);\n" 23 | "}\n" 24 | ; 25 | const char *fss = "" 26 | "precision mediump float;\n" 27 | "in vec4 v_col;\n" 28 | "out vec4 out_color;\n" 29 | "void main() {\n" 30 | " out_color = v_col;\n" 31 | "}\n" 32 | ; 33 | const char *attribs[] = { 34 | "a_position", 35 | "a_color", 36 | NULL 37 | }; 38 | 39 | tri_res.prog = tfx_program_new(vss, fss, attribs, -1); 40 | 41 | float verts[] = { 42 | 0.0f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 43 | -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 44 | 0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f 45 | }; 46 | 47 | tfx_vertex_format fmt = tfx_vertex_format_start(); 48 | tfx_vertex_format_add(&fmt, 0, 3, false, TFX_TYPE_FLOAT); 49 | tfx_vertex_format_add(&fmt, 1, 4, true, TFX_TYPE_FLOAT); 50 | tfx_vertex_format_end(&fmt); 51 | 52 | tri_res.vbo = tfx_buffer_new(verts, sizeof(verts), &fmt, TFX_BUFFER_NONE); 53 | } 54 | 55 | void triangle_frame() { 56 | const uint8_t back = 1; 57 | tfx_set_vertices(&tri_res.vbo, 3); 58 | tfx_set_state(0 59 | | TFX_STATE_RGB_WRITE 60 | | TFX_STATE_ALPHA_WRITE 61 | | TFX_STATE_BLEND_ALPHA 62 | ); 63 | tfx_submit(back, tri_res.prog, false); 64 | tfx_frame(); 65 | } 66 | 67 | void triangle_deinit() { 68 | tfx_buffer_free(&tri_res.vbo); 69 | // tfx_program_free(tri_res.prog); // NYI 70 | } 71 | -------------------------------------------------------------------------------- /examples/01-triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakesoda/tinyfx/e67ef7f5d7d41f717683b30ec3644d3ba26dff23/examples/01-triangle.png -------------------------------------------------------------------------------- /examples/02-sky.c: -------------------------------------------------------------------------------- 1 | #include "tinyfx.h" 2 | #include "demo_util.h" 3 | #include 4 | 5 | struct { 6 | tfx_program prog; 7 | tfx_uniform world_from_screen; // inverse(p*v) 8 | tfx_uniform sun_params; 9 | float aspect; 10 | float proj[16]; 11 | int pitch; 12 | int yaw; 13 | } sky_res; 14 | 15 | void sky_init(uint16_t w, uint16_t h) { 16 | sky_res.pitch = 0; 17 | sky_res.yaw = 0; 18 | sky_res.aspect = (float)w / (float)h; 19 | 20 | const uint8_t back = 1; 21 | tfx_view_set_clear_color(back, 0x555555ff); 22 | tfx_view_set_name(back, "Sky Pass"); 23 | 24 | const char *src = demo_read_file("examples/02-sky.glsl"); 25 | const char *attribs[] = { "a_position", NULL }; 26 | 27 | sky_res.prog = tfx_program_new(src, src, attribs, -1); 28 | 29 | sky_res.world_from_screen = tfx_uniform_new("u_world_from_screen", TFX_UNIFORM_MAT4, 1); 30 | sky_res.sun_params = tfx_uniform_new("u_sun_params", TFX_UNIFORM_VEC4, 1); 31 | 32 | mat4_projection(sky_res.proj, 140.0f, sky_res.aspect, 0.1f, 1000.0f, false); 33 | } 34 | 35 | void sky_frame(int mx, int my) { 36 | const uint8_t back = 1; 37 | 38 | sky_res.pitch += my; 39 | sky_res.yaw -= mx; 40 | 41 | const float sensitivity = 0.1f; 42 | const float limit = 3.1415962f * 0.5f; 43 | const float pitch = clamp(to_rad((float)sky_res.pitch * sensitivity), -limit, limit); 44 | const float yaw = to_rad((float)sky_res.yaw * sensitivity); 45 | 46 | const float eye[3] = { 0.0f, 0.0f, 0.0f }; 47 | float at[3] = { 48 | eye[0] + cosf(yaw), 49 | eye[1] + sinf(yaw), 50 | eye[2] + sinf(pitch) 51 | }; 52 | const float up[3] = { 0.0f, 0.0f, 1.0f }; 53 | float view[16]; 54 | mat4_lookat(view, at, eye, up); 55 | 56 | float tmp[16], inv[16]; 57 | mat4_mul(tmp, view, sky_res.proj); 58 | mat4_invert(inv, tmp); 59 | tfx_set_uniform(&sky_res.world_from_screen, inv, 1); 60 | 61 | float dir[3] = { 0.707f, 0.0f, 0.707f }; 62 | float sun[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; 63 | vec_norm(sun, dir, 3); 64 | tfx_set_uniform(&sky_res.sun_params, sun, 1); 65 | 66 | tfx_transient_buffer tb = demo_screen_triangle(1.0f); 67 | tfx_set_transient_buffer(tb); 68 | tfx_set_state(TFX_STATE_RGB_WRITE | TFX_STATE_DEPTH_WRITE); 69 | tfx_submit(back, sky_res.prog, false); 70 | tfx_frame(); 71 | } 72 | 73 | void sky_deinit() {} 74 | -------------------------------------------------------------------------------- /examples/02-sky.glsl: -------------------------------------------------------------------------------- 1 | #ifdef VERTEX 2 | in vec3 v_position; 3 | 4 | out vec3 f_position; 5 | 6 | uniform mat4 u_world_from_screen; 7 | 8 | void main() { 9 | gl_Position = vec4(v_position.xy, 2.0 * step(0.5, v_position.z) - 1.0, 1.0); 10 | f_position = mat3(u_world_from_screen) * vec3(v_position.x, v_position.y, gl_Position.z); 11 | } 12 | #endif 13 | 14 | #ifdef PIXEL 15 | precision highp float; 16 | 17 | in vec3 f_position; 18 | 19 | out vec4 out_color; 20 | 21 | uniform vec4 u_sun_params; 22 | const bool u_tonemap = true; 23 | 24 | //camera information, should be a more global part vs in sky; but we don't have that yet. 25 | //xyz below is white point, feels like inverse of normal color, low values is more of that color 26 | //higher values is darker. w is the exposure, -values and zero are acceptable inputs. 27 | const vec4 cameraWhiteAndExposure = vec4(5.0, 5.0, 5.0, 1.0); 28 | 29 | const vec3 luma = vec3(0.299, 0.587, 0.114); 30 | const vec3 cameraPos = vec3(0.0, 0.0, 0.0); 31 | const float luminance = 1.05; // formerly uniform 32 | const float turbidity = 8.0; // formerly uniform 33 | const float reileigh = 1.25; // formerly uniform 34 | const float mieCoefficient = 0.005; 35 | const float mieDirectionalG = 0.8; 36 | 37 | // constants for atmospheric scattering 38 | const float e = 2.71828182845904523536028747135266249775724709369995957; 39 | const float pi = 3.141592653589793238462643383279502884197169; 40 | 41 | // refractive index of air 42 | const float n = 1.0003; 43 | 44 | // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius) 45 | const float N = 2.545E25; 46 | 47 | // depolatization factor for standard air 48 | const float pn = 0.035; 49 | 50 | // wavelength of used primaries, according to preetham 51 | const vec3 lambda = vec3(680E-9, 550E-9, 450E-9); 52 | 53 | // mie stuff 54 | // K coefficient for the primaries 55 | const vec3 K = vec3(0.686, 0.678, 0.666); 56 | const float v = 4.0; 57 | 58 | // optical length at zenith for molecules 59 | const float rayleighZenithLength = 8.4E3; 60 | const float mieZenithLength = 1.25E3; 61 | const vec3 up = vec3(0.0, 0.0, 1.0); 62 | 63 | const float EE = 1000.0; 64 | const float sunAngularDiameterCos = 0.99996192306417*0.9995; // probably correct size, maybe 65 | 66 | // earth shadow hack 67 | const float steepness = 1.5; 68 | 69 | float to_gamma(float v) { return pow(v, 1.0/2.2); } 70 | vec3 to_gamma(vec3 v) { return pow(v, vec3(1.0/2.2)); } 71 | vec4 to_gamma(vec4 v) { return vec4(pow(v.rgb, vec3(1.0/2.2)), v.a); } 72 | 73 | vec3 tonemap_aces(vec3 x) { 74 | float a = 2.51; 75 | float b = 0.03; 76 | float c = 2.43; 77 | float d = 0.59; 78 | float e = 0.14; 79 | return clamp((x*(a*x+b))/(x*(c*x+d)+e), 0.0, 1.0); 80 | } 81 | 82 | vec3 totalRayleigh(vec3 lambda) { 83 | return (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)); 84 | } 85 | 86 | // see http://blenderartists.org/forum/showthread.php?321110-Shaders-and-Skybox-madness 87 | // A simplied version of the total Rayleigh scattering to works on browsers that use ANGLE 88 | vec3 simplifiedRayleigh() { 89 | return 0.00054532832366 / vec3(94.0, 40.0, 18.0); 90 | } 91 | 92 | float rayleighPhase(float cosTheta) { 93 | return (3.0 / (16.0*pi)) * (1.0 + pow(cosTheta, 2.0)); 94 | } 95 | 96 | vec3 totalMie(vec3 lambda, vec3 K, float T) { 97 | float c = (0.2 * T ) * 10E-18; 98 | return 0.434 * c * pi * pow((2.0 * pi) / lambda, vec3(v - 2.0)) * K; 99 | } 100 | 101 | float hgPhase(float cosTheta, float g) { 102 | return (1.0 / (4.0*pi)) * ((1.0 - pow(g, 2.0)) / pow(1.0 - 2.0*g*cosTheta + pow(g, 2.0), 1.5)); 103 | } 104 | 105 | float sunIntensity(float zenithAngleCos) { 106 | // See https://github.com/mrdoob/three.js/issues/8382 107 | float cutoffAngle = pi/1.95; 108 | return EE * max(0.0, 1.0 - pow(e, -((cutoffAngle - acos(zenithAngleCos))/steepness))); 109 | } 110 | 111 | void main() { 112 | float sunfade = 1.0-clamp(1.0-exp((u_sun_params.y/450000.0)),0.0,1.0); 113 | float reileighCoefficient = reileigh - (1.0* (1.0-sunfade)); 114 | vec3 sunDirection = normalize(u_sun_params.xyz); 115 | float sunE = sunIntensity(dot(sunDirection, up)); 116 | 117 | // extinction (absorbtion + out scattering) 118 | // rayleigh coefficients 119 | vec3 betaR = simplifiedRayleigh() * reileighCoefficient; 120 | 121 | // mie coefficients 122 | vec3 betaM = totalMie(lambda, K, turbidity) * mieCoefficient; 123 | 124 | // optical length 125 | // cutoff angle at 90 to avoid singularity in next formula. 126 | float zenithAngle = acos(max(0.0, dot(up, normalize(f_position.xyz - cameraPos)))); 127 | float sR = rayleighZenithLength / (cos(zenithAngle) + 0.15 * pow(93.885 - ((zenithAngle * 180.0) / pi), -1.253)); 128 | float sM = mieZenithLength / (cos(zenithAngle) + 0.15 * pow(93.885 - ((zenithAngle * 180.0) / pi), -1.253)); 129 | 130 | // combined extinction factor 131 | vec3 Fex = exp(-(betaR * sR + betaM * sM)); 132 | 133 | // in scattering 134 | float cosTheta = dot(normalize(f_position.xyz - cameraPos), sunDirection); 135 | 136 | float rPhase = rayleighPhase(cosTheta*0.5+0.5); 137 | vec3 betaRTheta = betaR * rPhase; 138 | 139 | float mPhase = hgPhase(cosTheta, mieDirectionalG); 140 | vec3 betaMTheta = betaM * mPhase; 141 | 142 | vec3 Lin = pow(sunE * ((betaRTheta + betaMTheta) / (betaR + betaM)) * (1.0 - Fex),vec3(1.5)); 143 | float sun_dot_up = dot(up, sunDirection); 144 | 145 | Lin *= mix(vec3(1.0),pow(sunE * ((betaRTheta + betaMTheta) / (betaR + betaM)) * Fex,vec3(1.0/2.0)),clamp(pow(1.0-sun_dot_up,5.0),0.0,1.0)); 146 | 147 | // night sky 148 | vec3 direction = normalize(f_position.xyz - cameraPos); 149 | float theta = acos(direction.y); // elevation --> y-axis, [-pi/2, pi/2] 150 | float phi = atan(direction.z/direction.x); // azimuth --> x-axis [-pi/2, pi/2] 151 | vec3 L0 = vec3(0.1) * Fex; 152 | 153 | // composition + solar disc 154 | float sundisk = smoothstep(sunAngularDiameterCos,sunAngularDiameterCos+0.001,cosTheta); 155 | L0 += (sunE * 19000.0 * Fex)*sundisk; 156 | 157 | vec3 texColor = (Lin+L0); 158 | texColor *= 0.04; 159 | texColor += vec3(0.0,0.001,0.0025)*0.3; 160 | 161 | vec3 color = log2(2.0/pow(luminance,4.0))*texColor; 162 | 163 | vec3 retColor = pow(color,vec3(1.0/(1.2+(1.2*sunfade)))); 164 | 165 | retColor = mix(retColor * 0.75, retColor, clamp(dot(direction, up) * 0.5 + 0.5, 0.0, 1.0)); 166 | 167 | vec3 final = pow(retColor * 0.75, vec3(2.2)); 168 | 169 | // if (!u_tonemap) { 170 | // gl_FragColor = vec4(final, 1.0); 171 | // return; 172 | // } 173 | 174 | vec3 white_point = cameraWhiteAndExposure.rgb; 175 | vec3 white = tonemap_aces(vec3(1000.0)); 176 | final *= exp2(cameraWhiteAndExposure.a); 177 | final = tonemap_aces(final/white_point)*white; 178 | out_color = to_gamma(vec4(final, 1.0)); 179 | } 180 | 181 | #endif 182 | -------------------------------------------------------------------------------- /examples/02-sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakesoda/tinyfx/e67ef7f5d7d41f717683b30ec3644d3ba26dff23/examples/02-sky.png -------------------------------------------------------------------------------- /examples/08-water-sky.glsl: -------------------------------------------------------------------------------- 1 | #ifdef VERTEX 2 | uniform mat4 u_screen_from_view; 3 | uniform mat4 u_view_from_world; 4 | 5 | in vec3 position; 6 | 7 | out vec3 view_dir_ws; 8 | 9 | void main() { 10 | vec4 vertex = vec4(position, 1.0); 11 | vertex *= 2.0; 12 | mat4 inv_vp = inverse(u_screen_from_view * mat4(mat3(u_view_from_world))); 13 | view_dir_ws = -(inv_vp * vec4(vertex.x, vertex.y, 1.0, 1.0)).xyz; 14 | vertex.z = 1.0; // force to the far plane 15 | gl_Position = vec4(vertex.xyz, 1.0); 16 | } 17 | #endif 18 | 19 | #ifdef PIXEL 20 | uniform vec4 u_sun_params; 21 | #define sun_direction (u_sun_params.xyz) 22 | 23 | in vec3 view_dir_ws; 24 | 25 | out vec4 out_color; 26 | 27 | float square(float v) { return v*v; } 28 | float cube(float v) { return v*v*v; } 29 | 30 | // extra_cheap_atmosphere adapted from https://www.shadertoy.com/view/MdXyzX 31 | vec3 extra_cheap_atmosphere(vec3 i_ws, vec3 sun_ws) { 32 | sun_ws.z = max(sun_ws.z, -0.07); 33 | float special_trick = 1.0 / (i_ws.z * 1.0 + 0.125); 34 | float special_trick2 = 1.0 / (sun_ws.z * 11.0 + 1.0); 35 | float raysundt = square(abs(dot(sun_ws, i_ws))); 36 | float sundt = pow(max(0.0, dot(sun_ws, i_ws)), 8.0); 37 | float mymie = sundt * special_trick * 0.025; 38 | vec3 suncolor = mix(vec3(1.0), max(vec3(0.0), vec3(1.0) - vec3(5.5, 13.0, 22.4) / 22.4), special_trick2); 39 | vec3 bluesky= vec3(5.5, 13.0, 22.4) / 22.4 * suncolor; 40 | vec3 bluesky2 = max(vec3(0.0), bluesky - vec3(5.5, 13.0, 22.4) * 0.002 * (special_trick + -6.0 * sun_ws.z * sun_ws.z)); 41 | bluesky2 *= special_trick * (0.4 + raysundt * 0.4); 42 | return max(vec3(0.0), bluesky2 * (1.0 + 1.0 * cube(1.0 - i_ws.z)) + mymie * suncolor); 43 | } 44 | 45 | vec3 sun(vec3 i_ws, vec3 sun_ws) { 46 | vec3 sun_color = vec3(1000.0); 47 | float sun_angle = dot(sun_ws, i_ws); 48 | vec3 halo = normalize(vec3(6.0, 7.0, 8.0)); 49 | float halo_a = pow(sun_angle * 0.5 + 0.5, 1.0) * 0.125; 50 | float sun_size = 0.9999; 51 | float halo_b = (1.0-pow(smoothstep(sun_size, sun_size - 0.5, sun_angle), 0.025)) * 5.0; 52 | halo *= (0.00125 /*1.25 * 1.0/length(sun_color)*/) * (halo_a + halo_b); 53 | return sun_color * halo + sun_color * smoothstep(sun_size, 1.0, sun_angle); 54 | } 55 | 56 | vec3 sky_approx(vec3 i_ws, vec3 sun_ws) { 57 | vec3 final = extra_cheap_atmosphere(i_ws, sun_ws); 58 | final += sun(i_ws, sun_ws); 59 | vec3 up = vec3(0.0, 0.0, 1.0); 60 | final = mix( // earth shadow 61 | final, 62 | vec3(2.0 * dot(sun_ws, up)), 63 | smoothstep(0.25, -0.1, dot(i_ws, up)) 64 | ); 65 | return final * 3.3635856610149; //exp2(1.75); 66 | } 67 | 68 | vec3 tonemap_aces(vec3 x) { 69 | float a = 2.51; 70 | float b = 0.03; 71 | float c = 2.43; 72 | float d = 0.59; 73 | float e = 0.14; 74 | return clamp((x*(a*x+b))/(x*(c*x+d)+e), 0.0, 1.0); 75 | } 76 | 77 | #ifdef GL_ES 78 | vec4 linear_to_gamma(vec4 c) { 79 | c.rgb = pow(c.rgb, vec3(2.2)); 80 | return c; 81 | } 82 | #else 83 | vec4 linear_to_gamma(vec4 c) { 84 | bvec3 leq = lessThanEqual(c.rgb, vec3(0.04045)); 85 | c.r = leq.r ? c.r / 12.92 : pow((c.r + 0.055) / 1.055, 2.4); 86 | c.g = leq.g ? c.g / 12.92 : pow((c.g + 0.055) / 1.055, 2.4); 87 | c.b = leq.b ? c.b / 12.92 : pow((c.b + 0.055) / 1.055, 2.4); 88 | return c; 89 | } 90 | #endif 91 | 92 | void main() { 93 | vec3 sun_ws = normalize(-sun_direction); 94 | vec3 i_ws = normalize(-view_dir_ws); 95 | vec3 final = sky_approx(i_ws, sun_ws); 96 | vec3 screen = final; 97 | //screen.rgb *= exp2(-1.75); 98 | screen.rgb *= 0.29730177875068; 99 | // normalize(vec3(5.0, 5.5, 5.25)); 100 | screen.rgb /= vec3(0.54944225579476, 0.60438648137423, 0.5769143685845); 101 | screen.rgb = tonemap_aces(screen.rgb);// / tonemap_aces(vec3(1000.0)); 102 | out_color = linear_to_gamma(vec4(screen, 1.0)); 103 | } 104 | #endif 105 | -------------------------------------------------------------------------------- /examples/08-water.c: -------------------------------------------------------------------------------- 1 | #include "tinyfx.h" 2 | #include "demo_util.h" 3 | #include 4 | 5 | struct { 6 | tfx_program prog; 7 | tfx_program sky; 8 | tfx_uniform world_from_local; // inverse(p*v) 9 | tfx_uniform screen_from_view; 10 | tfx_uniform view_from_world; 11 | tfx_uniform world_from_screen; 12 | tfx_uniform sun_params; 13 | tfx_buffer grid; 14 | int grid_vertices; 15 | float aspect; 16 | float proj[16]; 17 | int pitch; 18 | int yaw; 19 | double start; 20 | } water_res; 21 | 22 | struct vert { 23 | float p[3]; 24 | }; 25 | 26 | void write_vertex(struct vert *out, float x, float y, float tiles) { 27 | out->p[0] = x / tiles; 28 | out->p[1] = y / tiles; 29 | out->p[2] = 0.0f; 30 | } 31 | 32 | void water_init(uint16_t w, uint16_t h) { 33 | water_res.start = (double)SDL_GetPerformanceCounter() / (double)SDL_GetPerformanceFrequency(); 34 | 35 | water_res.pitch = 0; 36 | water_res.yaw = 0; 37 | water_res.aspect = (float)w / (float)h; 38 | 39 | const uint8_t back = 1; 40 | tfx_view_set_clear_color(back, 0x555555ff); 41 | tfx_view_set_clear_depth(back, 1.0f); 42 | tfx_view_set_depth_test(back, TFX_DEPTH_TEST_LT); 43 | tfx_view_set_name(back, "Water Pass"); 44 | 45 | const char *attribs[] = { "a_position", NULL }; 46 | 47 | const char *src = demo_read_file("examples/08-water.glsl"); 48 | water_res.prog = tfx_program_new(src, src, attribs, -1); 49 | free(src); 50 | 51 | const char *src2 = demo_read_file("examples/08-water-sky.glsl"); 52 | water_res.sky = tfx_program_new(src2, src2, attribs, -1); 53 | free(src2); 54 | 55 | water_res.world_from_local = tfx_uniform_new("u_world_from_local", TFX_UNIFORM_MAT4, 1); 56 | water_res.screen_from_view = tfx_uniform_new("u_screen_from_view", TFX_UNIFORM_MAT4, 1); 57 | water_res.view_from_world = tfx_uniform_new("u_view_from_world", TFX_UNIFORM_MAT4, 1); 58 | water_res.world_from_screen = tfx_uniform_new("u_world_from_screen", TFX_UNIFORM_MAT4, 1); 59 | 60 | water_res.sun_params = tfx_uniform_new("u_sun_params", TFX_UNIFORM_VEC4, 1); 61 | 62 | mat4_projection(water_res.proj, 55.0f, water_res.aspect, 0.1f, 1000.0f, false); 63 | 64 | tfx_vertex_format fmt = tfx_vertex_format_start(); 65 | tfx_vertex_format_add(&fmt, 0, 3, false, TFX_TYPE_FLOAT); 66 | tfx_vertex_format_end(&fmt); 67 | 68 | float div = 8.0f; 69 | float tiles = fmaxf(0.0f, floorf((float)w / div)) + 1.0f; 70 | water_res.grid_vertices = tiles * tiles * 6; 71 | struct vert *verts = malloc(fmt.stride * water_res.grid_vertices); 72 | 73 | int i = 0; 74 | for (int y = 1; y < tiles; y++) { 75 | for (int x = 1; x < tiles; x++) { 76 | write_vertex(&verts[i++], x-1, y-1, tiles); 77 | write_vertex(&verts[i++], x-1, y, tiles); 78 | write_vertex(&verts[i++], x, y-1, tiles); 79 | 80 | write_vertex(&verts[i++], x-1, y, tiles); 81 | write_vertex(&verts[i++], x, y, tiles); 82 | write_vertex(&verts[i++], x, y-1, tiles); 83 | } 84 | } 85 | 86 | water_res.grid = tfx_buffer_new( 87 | verts, 88 | fmt.stride * water_res.grid_vertices, 89 | &fmt, 90 | TFX_BUFFER_NONE 91 | ); 92 | 93 | free(verts); 94 | } 95 | 96 | void water_frame(int mx, int my) { 97 | const uint8_t back = 1; 98 | 99 | water_res.pitch += my; 100 | water_res.yaw -= mx; 101 | 102 | const float sensitivity = 0.1f; 103 | const float limit = 3.1415962f * 0.5f; 104 | const float pitch = clamp(to_rad((float)water_res.pitch * sensitivity), -limit, limit); 105 | const float yaw = to_rad((float)water_res.yaw * sensitivity); 106 | 107 | const float eye[3] = { 0.0f, 0.0f, 5.75f }; 108 | float at[3] = { 109 | eye[0] + cosf(yaw), 110 | eye[1] + sinf(yaw), 111 | eye[2] + sinf(pitch) 112 | }; 113 | const float up[3] = { 0.0f, 0.0f, 1.0f }; 114 | float view[16]; 115 | mat4_lookat(view, at, eye, up); 116 | 117 | float tmp[16], inv[16]; 118 | mat4_mul(tmp, view, water_res.proj); 119 | mat4_invert(inv, tmp); 120 | 121 | float model[16] = { 122 | 1.0f, 0.0f, 0.0f, 0.0f, 123 | 0.0f, 1.0f, 0.0f, 0.0f, 124 | 0.0f, 0.0f, 1.0f, 0.0f, 125 | 0.0f, 0.0f, 0.0f, 1.0f 126 | }; 127 | tfx_set_uniform(&water_res.world_from_local, model, 1); 128 | tfx_set_uniform(&water_res.view_from_world, view, 1); 129 | tfx_set_uniform(&water_res.screen_from_view, water_res.proj, 1); 130 | tfx_set_uniform(&water_res.world_from_screen, inv, 1); 131 | 132 | double now = (double)SDL_GetPerformanceCounter() / (double)SDL_GetPerformanceFrequency(); 133 | 134 | float dir[3] = { 1.0f, 0.5f, -0.25f }; 135 | float sun[4] = { 0.0f, 0.0f, 0.0f, (float)(now - water_res.start) }; 136 | vec_norm(sun, dir, 3); 137 | tfx_set_uniform(&water_res.sun_params, sun, 1); 138 | 139 | tfx_set_transient_buffer(demo_screen_triangle(1.0f)); 140 | tfx_set_state(TFX_STATE_RGB_WRITE); 141 | tfx_submit(back, water_res.sky, false); 142 | 143 | tfx_set_vertices(&water_res.grid, water_res.grid_vertices); 144 | tfx_set_state(TFX_STATE_RGB_WRITE | TFX_STATE_DEPTH_WRITE | TFX_STATE_CULL_CCW); 145 | tfx_submit(back, water_res.prog, false); 146 | 147 | tfx_frame(); 148 | } 149 | 150 | void water_deinit() { 151 | tfx_buffer_free(&water_res.grid); 152 | } 153 | -------------------------------------------------------------------------------- /examples/08-water.glsl: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | #ifdef VERTEX 4 | uniform vec4 u_sun_params; 5 | #define time (u_sun_params.w) 6 | uniform mat4 u_screen_from_view; 7 | uniform mat4 u_view_from_world; 8 | uniform mat4 u_world_from_local; 9 | 10 | in vec4 screen_vertex; 11 | 12 | out vec2 local_uv; 13 | out vec3 view_dir_vs; 14 | out vec3 view_dir_ws; 15 | out mat3 view_from_world; 16 | out float far_clip; 17 | #endif 18 | 19 | #ifdef PIXEL 20 | uniform vec4 u_sun_params; 21 | #define sun_direction (u_sun_params.xyz) 22 | #define time (u_sun_params.w) 23 | 24 | in vec2 local_uv; 25 | in vec3 view_dir_vs; 26 | in vec3 view_dir_ws; 27 | in mat3 view_from_world; 28 | in float far_clip; 29 | 30 | out vec4 out_color; 31 | #endif 32 | 33 | // these 3 functions adapted from https://www.shadertoy.com/view/MdXyzX 34 | vec2 wavedx(vec2 position, vec2 direction, float speed, float frequency, float timeshift) { 35 | float x = dot(direction, position) * frequency + timeshift * speed; 36 | float wave = exp(sin(x) - 1.0); 37 | float dx = wave * cos(x); 38 | return vec2(wave, -dx); 39 | } 40 | 41 | float getwaves(vec2 position) { 42 | const float drag_mult = 0.048; 43 | const int iterations = 40; 44 | // iter, w, ws state 45 | vec3 accum = vec3(0.0, 0.0, 0.0); 46 | // phase, speed, weight 47 | vec3 psw = vec3(6.0, 2.0, 1.0); 48 | for (int i=0; i < iterations; i++) { 49 | // note: vec2(sin(), cos()) will always be normalized 50 | vec2 p = vec2(sin(accum.x), cos(accum.x)); 51 | vec2 res = wavedx(position, p, psw.y, psw.x, time); 52 | position += p * res.y * psw.z * drag_mult; 53 | accum += vec3(12.0, res.x * psw.z, psw.z); 54 | psw *= vec3(1.18, 1.07, 0.8); 55 | } 56 | return accum.y / accum.z; 57 | } 58 | 59 | vec3 normal(vec2 pos, float e, float depth) { 60 | vec2 ex = vec2(e * 0.1, 0.0); 61 | float H = getwaves(pos.xy * 0.1) * depth; 62 | vec3 a = vec3(pos.x, H, pos.y); 63 | return normalize( 64 | cross( 65 | (a-vec3(pos.x - e, getwaves(pos.xy * 0.1 - ex.xy) * depth, pos.y)), 66 | (a-vec3(pos.x, getwaves(pos.xy * 0.1 + ex.yx) * depth, pos.y + e)) 67 | ) 68 | ); 69 | } 70 | 71 | #ifdef VERTEX 72 | struct RayPlane { 73 | vec3 pos; 74 | vec3 dir; 75 | }; 76 | 77 | vec4 ray_plane(RayPlane ray, RayPlane plane) { 78 | float t = dot(plane.pos - ray.pos, plane.dir) / dot(plane.dir, ray.dir); 79 | return mix( 80 | vec4(0.0, 0.0, 0.0, -1.0), // clip 81 | vec4(ray.pos + ray.dir * t, 1.0), 82 | step(t, 0.0) 83 | ); 84 | } 85 | 86 | float deform(vec2 coord) { 87 | float size = 10.0; 88 | vec2 uv = coord / size; 89 | local_uv = uv; 90 | float depth = 1.2; 91 | float offset = depth * getwaves(uv); 92 | return mix(offset, 0.0, min(1.0, distance(coord / 250.0, vec2(0.0)))); 93 | } 94 | 95 | vec2 correct_screen_uv(vec2 uv) { 96 | float overscan = 1.2; 97 | float squish = u_screen_from_view[0].x; 98 | float squash = u_screen_from_view[1].y; 99 | // edge case: square render target 100 | if (abs(squish - squash) < 0.01) { 101 | return uv * overscan * 1.1; 102 | } 103 | return vec2((uv.x - 0.5) * overscan * squash, (uv.y - 0.5) * overscan * squish); 104 | } 105 | 106 | void main() { 107 | vec2 screen_uv = screen_vertex.xy * 2.0 - 1.0; 108 | screen_uv = correct_screen_uv(screen_uv); 109 | screen_uv.y *= -1.0; 110 | 111 | RayPlane ray; 112 | mat3 rot = mat3(u_view_from_world); 113 | ray.pos = -u_view_from_world[3].xyz * rot; 114 | ray.dir = normalize(vec3(screen_uv, u_screen_from_view[0].x) * rot); 115 | 116 | RayPlane plane; 117 | plane.pos = (u_world_from_local * vec4(0.0, 0.0, 0.0, 1.0)).xyz; 118 | plane.dir = vec3(0.0, 0.0, 1.0); 119 | 120 | vec4 vertex = ray_plane(ray, plane); 121 | if (vertex.w < 0.0) { 122 | gl_Position = vertex; 123 | return; 124 | } 125 | vertex.z = deform(vertex.xy); 126 | 127 | view_dir_vs = -(u_view_from_world * vertex).xyz; 128 | 129 | view_dir_ws = transpose(mat3(u_view_from_world)) * -view_dir_vs; 130 | view_from_world = mat3(u_view_from_world); 131 | 132 | float near = (2.0 * u_screen_from_view[3][2]) / (2.0 * u_screen_from_view[2][2] - 2.0); 133 | far_clip = ((u_screen_from_view[2][2] - 1.0) * near) / (u_screen_from_view[2][2] + 1.0); 134 | 135 | gl_Position = u_screen_from_view * u_view_from_world * vertex; 136 | } 137 | #endif 138 | 139 | #ifdef PIXEL 140 | float square(float v) { return v*v; } 141 | float cube(float v) { return v*v*v; } 142 | 143 | vec3 extra_cheap_atmosphere(vec3 i_ws, vec3 sun_ws, float sdi) { 144 | sun_ws.z = max(sun_ws.z, -0.07); 145 | float special_trick = 1.0 / (i_ws.z * 1.0 + 0.125); 146 | float special_trick2 = 1.0 / (sun_ws.z * 11.0 + 1.0); 147 | float raysundt = square(abs(sdi)); 148 | float sundt = pow(max(0.0, sdi), 8.0); 149 | float mymie = sundt * special_trick * 0.025; 150 | vec3 suncolor = mix(vec3(1.0), max(vec3(0.0), vec3(1.0) - vec3(5.5, 13.0, 22.4) / 22.4), special_trick2); 151 | vec3 bluesky= vec3(5.5, 13.0, 22.4) / 22.4 * suncolor; 152 | vec3 bluesky2 = max(vec3(0.0), bluesky - vec3(5.5, 13.0, 22.4) * 0.002 * (special_trick + -6.0 * sun_ws.z * sun_ws.z)); 153 | bluesky2 *= special_trick * (0.4 + raysundt * 0.4); 154 | return max(vec3(0.0), bluesky2 * (1.0 + 1.0 * cube(1.0 - i_ws.z)) + mymie * suncolor); 155 | } 156 | 157 | vec3 sun(vec3 i_ws, vec3 sun_ws, float sdi) { 158 | vec3 sun_color = vec3(1000.0); 159 | float sun_angle = sdi; 160 | // normalize(vec3(6.0, 7.0, 8.0)); 161 | vec3 halo = vec3(0.49153915231142, 0.57346234436333, 0.65538553641523); 162 | float halo_a = (sun_angle * 0.5 + 0.5, 1.0) * 0.125; 163 | float sun_size = 0.9999; 164 | float halo_b = (1.0-pow(smoothstep(sun_size, sun_size - 0.5, sun_angle), 0.025)) * 5.0; 165 | halo *= (0.00125 /*1.25 * 1.0/length(sun_color)*/) * (halo_a + halo_b); 166 | return sun_color * halo + sun_color * smoothstep(sun_size, 1.0, sun_angle); 167 | } 168 | 169 | vec3 sky_approx(vec3 i_ws, vec3 sun_ws) { 170 | float sdi = dot(sun_ws, i_ws); 171 | vec3 final = extra_cheap_atmosphere(i_ws, sun_ws, sdi); 172 | final += sun(i_ws, sun_ws, sdi); 173 | final = mix( // earth shadow 174 | final, 175 | vec3(3.0 * sun_ws.z) * vec3(0.3, 0.6, 1.0), 176 | smoothstep(0.25, -0.1, i_ws.z) 177 | ); 178 | return final * 3.3635856610149; //exp2(1.75); 179 | } 180 | 181 | float schlick_ior_fresnel(float ior, float ldh) { 182 | float f0 = (ior-1.0)/(ior+1.0); 183 | f0 *= f0; 184 | float x = clamp(1.0-ldh, 0.0, 1.0); 185 | float x2 = x*x; 186 | return f0 + (1.0 - f0) * (x2*x2*x); 187 | } 188 | 189 | vec3 tonemap_aces(vec3 x) { 190 | float a = 2.51; 191 | float b = 0.03; 192 | float c = 2.43; 193 | float d = 0.59; 194 | float e = 0.14; 195 | return clamp((x*(a*x+b))/(x*(c*x+d)+e), 0.0, 1.0); 196 | } 197 | 198 | #ifdef GL_ES 199 | vec4 linear_to_gamma(vec4 c) { 200 | c.rgb = pow(c.rgb, vec3(2.2)); 201 | return c; 202 | } 203 | #else 204 | vec4 linear_to_gamma(vec4 c) { 205 | bvec3 leq = lessThanEqual(c.rgb, vec3(0.04045)); 206 | c.r = leq.r ? c.r / 12.92 : pow((c.r + 0.055) / 1.055, 2.4); 207 | c.g = leq.g ? c.g / 12.92 : pow((c.g + 0.055) / 1.055, 2.4); 208 | c.b = leq.b ? c.b / 12.92 : pow((c.b + 0.055) / 1.055, 2.4); 209 | return c; 210 | } 211 | #endif 212 | 213 | void main() { 214 | vec3 n_ws = normal(local_uv, 0.001, 1.2).xzy; 215 | vec3 n_vs = (view_from_world*n_ws); 216 | vec3 i_vs = normalize(view_dir_vs); 217 | vec3 i_ws = normalize(view_dir_ws); 218 | float ldh = sqrt(max(0.05, dot(n_vs, i_vs))); 219 | float ndi = schlick_ior_fresnel(1.53, ldh); 220 | vec3 l_ws = normalize(sun_direction); 221 | vec3 final = mix(vec3(0.35, 0.50, 0.85), sky_approx(reflect(i_ws, n_ws), -l_ws).rgb, ndi); 222 | float fog = sqrt(min(1.0, length(view_dir_vs) / far_clip)); 223 | final = mix(final, sky_approx(i_ws, -l_ws), fog); 224 | vec3 screen = final; 225 | //screen.rgb *= exp2(-1.75); 226 | screen.rgb *= 0.29730177875068; 227 | // normalize(vec3(5.0, 5.5, 5.25)); 228 | screen.rgb /= vec3(0.54944225579476, 0.60438648137423, 0.5769143685845); 229 | screen.rgb = tonemap_aces(screen.rgb);// / tonemap_aces(vec3(1000.0)); 230 | out_color = linear_to_gamma(vec4(screen, 1.0)); 231 | } 232 | #endif 233 | -------------------------------------------------------------------------------- /examples/08-water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shakesoda/tinyfx/e67ef7f5d7d41f717683b30ec3644d3ba26dff23/examples/08-water.png -------------------------------------------------------------------------------- /examples/demo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "demo_util.h" 7 | #include "01-triangle.c" 8 | #include "02-sky.c" 9 | #include "08-water.c" 10 | 11 | struct demo_def { 12 | void (*init)(uint16_t, uint16_t); 13 | void (*frame)(int, int); 14 | void (*cleanup)(); 15 | }; 16 | 17 | static const int num_demos = 3; 18 | static struct demo_def g_demos[] = { 19 | { &triangle_init, &triangle_frame, &triangle_deinit }, 20 | { &sky_init, &sky_frame, &sky_deinit }, 21 | { &water_init, &water_frame, &water_deinit } 22 | }; 23 | 24 | static int g_current_demo = 0; 25 | static int g_queue_demo = 0; 26 | 27 | static uint16_t g_width = 0; 28 | static uint16_t g_height = 0; 29 | static SDL_Window *g_window; 30 | static SDL_GLContext g_context; 31 | 32 | static int g_alive = 1; 33 | 34 | static int g_mx = 0; 35 | static int g_my = 0; 36 | 37 | static int demo_poll_events() { 38 | g_mx = 0; 39 | g_my = 0; 40 | SDL_Event event; 41 | while (SDL_PollEvent(&event)) { 42 | if (event.type == SDL_WINDOWEVENT 43 | && event.window.event == SDL_WINDOWEVENT_CLOSE 44 | ) { 45 | return 0; 46 | } 47 | switch (event.type) { 48 | case SDL_KEYDOWN: { 49 | if (event.key.keysym.sym == SDLK_ESCAPE) { 50 | return 0; 51 | } 52 | if (event.key.keysym.sym == SDLK_LEFT) { 53 | g_queue_demo -= 1; 54 | break; 55 | } 56 | if (event.key.keysym.sym == SDLK_RIGHT) { 57 | g_queue_demo += 1; 58 | break; 59 | } 60 | break; 61 | } 62 | case SDL_MOUSEMOTION: { 63 | g_mx += event.motion.xrel; 64 | g_my += event.motion.yrel; 65 | break; 66 | } 67 | default: break; 68 | } 69 | } 70 | return 1; 71 | } 72 | 73 | void demo_end_frame() { 74 | SDL_GL_SwapWindow(g_window); 75 | g_alive = demo_poll_events(); 76 | } 77 | 78 | void sigh(int signo) { 79 | if (signo == SIGINT) { 80 | g_alive = 0; 81 | puts(""); 82 | } 83 | 84 | // Make *SURE* that SDL gives input back to the OS. 85 | if (signo == SIGSEGV) { 86 | SDL_Quit(); 87 | } 88 | } 89 | 90 | typedef void (APIENTRY *DEBUGPROC)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, void* userParam); 91 | typedef void (APIENTRY *DEBUGMSG)(DEBUGPROC callback, const void* userParam); 92 | 93 | DEBUGMSG glDebugMessageCallback = 0; 94 | 95 | static void debug_spew(GLenum s, GLenum t, GLuint id, GLenum sev, GLsizei len, const GLchar *msg, void *p) { 96 | if (sev == GL_DEBUG_SEVERITY_NOTIFICATION || sev == GL_DEBUG_SEVERITY_LOW) { 97 | return; 98 | } 99 | printf("GL DEBUG: %s\n", msg); 100 | } 101 | 102 | int main(int argc, char **argv) { 103 | SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); 104 | g_window = SDL_CreateWindow( 105 | "", 106 | SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 107 | 640, 480, 108 | SDL_WINDOW_OPENGL 109 | ); 110 | SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); 111 | SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); 112 | SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); 113 | SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); 114 | SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); 115 | 116 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); 117 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); 118 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); 119 | SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG); 120 | 121 | g_context = SDL_GL_CreateContext(g_window); 122 | SDL_GL_GetDrawableSize( 123 | g_window, 124 | (int*)&g_width, 125 | (int*)&g_height 126 | ); 127 | SDL_GL_MakeCurrent(g_window, g_context); 128 | 129 | glDebugMessageCallback = (DEBUGMSG)SDL_GL_GetProcAddress("glDebugMessageCallback"); 130 | if (glDebugMessageCallback) { 131 | puts("GL debug enabled"); 132 | glDebugMessageCallback(&debug_spew, NULL); 133 | } 134 | 135 | printf("Display resolution: %dx%d\n", g_width, g_height); 136 | 137 | SDL_SetRelativeMouseMode(1); 138 | SDL_GL_SetSwapInterval(1); 139 | 140 | assert(signal(SIGINT, sigh) != SIG_ERR); 141 | 142 | tfx_platform_data pd; 143 | pd.use_gles = true; 144 | pd.context_version = 31; 145 | pd.gl_get_proc_address = SDL_GL_GetProcAddress; 146 | tfx_set_platform_data(pd); 147 | tfx_reset_flags flags = TFX_RESET_NONE 148 | | TFX_RESET_DEBUG_OVERLAY | TFX_RESET_DEBUG_OVERLAY_STATS 149 | | TFX_RESET_REPORT_GPU_TIMINGS 150 | ; 151 | tfx_reset(g_width, g_height, flags); 152 | 153 | if (g_demos[g_current_demo].init) { 154 | g_demos[g_current_demo].init(g_width, g_height); 155 | } 156 | 157 | double then = (double)SDL_GetPerformanceCounter() / (double)SDL_GetPerformanceFrequency(); 158 | double last_report = then; 159 | (void)last_report; // ignore warning 160 | while (g_alive) { 161 | double now = (double)SDL_GetPerformanceCounter() / (double)SDL_GetPerformanceFrequency(); 162 | double delta = now - then; 163 | (void)delta; 164 | then = now; 165 | 166 | #if 0 167 | if (now - last_report > 1.0) { 168 | printf("%0.2fms %0.1ffps\n", (float)(delta * 1000.0), (float)(1.0 / delta)); 169 | last_report = now; 170 | } 171 | #endif 172 | 173 | // on demo change, cleanup old and init new 174 | if (g_current_demo != g_queue_demo) { 175 | while (g_queue_demo < 0) { 176 | g_queue_demo += num_demos; 177 | } 178 | 179 | if (g_demos[g_current_demo].cleanup) { 180 | g_demos[g_current_demo].cleanup(); 181 | } 182 | 183 | g_queue_demo %= num_demos; 184 | g_current_demo = g_queue_demo; 185 | 186 | if (g_demos[g_current_demo].init) { 187 | tfx_reset(g_width, g_height, flags); 188 | g_demos[g_current_demo].init(g_width, g_height); 189 | } 190 | } 191 | 192 | if (g_demos[g_current_demo].frame) { 193 | g_demos[g_current_demo].frame(g_mx, g_my); 194 | } 195 | 196 | demo_end_frame(); 197 | } 198 | 199 | if (g_demos[g_current_demo].cleanup) { 200 | g_demos[g_current_demo].cleanup(); 201 | } 202 | 203 | tfx_shutdown(); 204 | 205 | SDL_Quit(); 206 | 207 | return 0; 208 | } 209 | -------------------------------------------------------------------------------- /examples/demo_util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include // file stuff 4 | #include // malloc 5 | #include 6 | #include "tinyfx.h" 7 | 8 | void *demo_read_file(const char *filename) { 9 | FILE *f = fopen(filename, "r"); 10 | if (f) { 11 | fseek(f, 0L, SEEK_END); 12 | int bytes = ftell(f); 13 | rewind(f); 14 | char *buf = (char*)malloc(bytes+1); 15 | buf[bytes] = '\0'; 16 | fread(buf, 1, bytes, f); 17 | fclose(f); 18 | 19 | return buf; 20 | } 21 | printf("Unable to open file \"%s\"\n", filename); 22 | return NULL; 23 | } 24 | 25 | tfx_transient_buffer demo_screen_triangle(float depth) { 26 | tfx_vertex_format fmt = tfx_vertex_format_start(); 27 | tfx_vertex_format_add(&fmt, 0, 3, false, TFX_TYPE_FLOAT); 28 | tfx_vertex_format_end(&fmt); 29 | 30 | tfx_transient_buffer tb = tfx_transient_buffer_new(&fmt, 3); 31 | float *fdata = (float*)tb.data; 32 | fdata[0] = fdata[3] = fdata[4] = fdata[7] = -1.0f; 33 | fdata[2] = fdata[5] = fdata[8] = depth; 34 | fdata[1] = fdata[6] = 3.0f; 35 | return tb; 36 | } 37 | 38 | // various useful math 39 | static float to_rad(const float deg) { 40 | return deg * (3.14159265358979323846f / 180.0f); 41 | } 42 | 43 | void mat4_projection(float out[16], float fovy, float aspect, float near, float far, bool infinite) { 44 | float t = tanf(to_rad(fovy) / 2.0f); 45 | float m22 = infinite ? 1.0f : -(far + near) / (far - near); 46 | float m23 = infinite ? 2.0f * near : -(2.0f * far * near) / (far - near); 47 | float m32 = infinite ? -1.0f : -1.0f; 48 | for (int i = 0; i < 16; i++) { out[i] = 0.0f; } 49 | out[0] = 1.0f / (t * aspect); 50 | out[5] = 1.0f / t; 51 | out[10] = m22; 52 | out[11] = m32; 53 | out[14] = m23; 54 | } 55 | 56 | float vec_dot(const float *a, const float *b, int len) { 57 | float sum = 0.0; 58 | for (int i = 0; i < len; i++) { 59 | sum += a[i] * b[i]; 60 | } 61 | return sum; 62 | } 63 | 64 | float vec_len(const float *in, int len) { 65 | return sqrtf(vec_dot(in, in, len)); 66 | } 67 | 68 | void vec_norm(float *out, const float *in, int len) { 69 | float vlen = vec_len(in, len); 70 | if (vlen == 0.0f) { 71 | for (int i = 0; i < len; i++) { 72 | out[i] = 0.0f; 73 | } 74 | return; 75 | } 76 | vlen = 1.0f / vlen; 77 | for (int i = 0; i < len; i++) { 78 | out[i] = in[i] * vlen; 79 | } 80 | } 81 | 82 | void vec3_cross(float *out, const float *a, const float *b) { 83 | out[0] = a[1] * b[2] - a[2] * b[1]; 84 | out[1] = a[2] * b[0] - a[0] * b[2]; 85 | out[2] = a[0] * b[1] - a[1] * b[0]; 86 | } 87 | 88 | void mat4_lookat(float out[16], const float eye[3], const float at[3], const float up[3]) { 89 | float forward[3] = { at[0] - eye[0], at[1] - eye[1], at[2] - eye[2] }; 90 | vec_norm(forward, forward, 3); 91 | 92 | float side[3]; 93 | vec3_cross(side, forward, up); 94 | vec_norm(side, side, 3); 95 | 96 | float new_up[3]; 97 | vec3_cross(new_up, side, forward); 98 | 99 | out[3] = out[7] = out[11] = 0.0f; 100 | out[0] = side[0]; 101 | out[1] = new_up[0]; 102 | out[2] = -forward[0]; 103 | out[4] = side[1]; 104 | out[5] = new_up[1]; 105 | out[6] = -forward[1]; 106 | out[8] = side[2]; 107 | out[9] = new_up[2]; 108 | out[10] = -forward[2]; 109 | out[12] = -vec_dot(side, eye, 3); 110 | out[13] = -vec_dot(new_up, eye, 3); 111 | out[14] = vec_dot(forward, eye, 3); 112 | out[15] = 1.0f; 113 | } 114 | 115 | void mat4_mul(float out[16], const float a[16], const float b[16]) { 116 | out[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12]; 117 | out[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13]; 118 | out[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14]; 119 | out[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15]; 120 | out[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12]; 121 | out[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13]; 122 | out[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14]; 123 | out[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15]; 124 | out[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12]; 125 | out[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13]; 126 | out[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14]; 127 | out[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15]; 128 | out[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12]; 129 | out[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13]; 130 | out[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14]; 131 | out[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15]; 132 | } 133 | 134 | void mat4_invert(float out[16], const float a[16]) { 135 | float tmp[16]; 136 | tmp[0] = a[5] * a[10] * a[15] - a[5] * a[11] * a[14] - a[9] * a[6] * a[15] + a[9] * a[7] * a[14] + a[13] * a[6] * a[11] - a[13] * a[7] * a[10]; 137 | tmp[1] = -a[1] * a[10] * a[15] + a[1] * a[11] * a[14] + a[9] * a[2] * a[15] - a[9] * a[3] * a[14] - a[13] * a[2] * a[11] + a[13] * a[3] * a[10]; 138 | tmp[2] = a[1] * a[6] * a[15] - a[1] * a[7] * a[14] - a[5] * a[2] * a[15] + a[5] * a[3] * a[14] + a[13] * a[2] * a[7] - a[13] * a[3] * a[6]; 139 | tmp[3] = -a[1] * a[6] * a[11] + a[1] * a[7] * a[10] + a[5] * a[2] * a[11] - a[5] * a[3] * a[10] - a[9] * a[2] * a[7] + a[9] * a[3] * a[6]; 140 | tmp[4] = -a[4] * a[10] * a[15] + a[4] * a[11] * a[14] + a[8] * a[6] * a[15] - a[8] * a[7] * a[14] - a[12] * a[6] * a[11] + a[12] * a[7] * a[10]; 141 | tmp[5] = a[0] * a[10] * a[15] - a[0] * a[11] * a[14] - a[8] * a[2] * a[15] + a[8] * a[3] * a[14] + a[12] * a[2] * a[11] - a[12] * a[3] * a[10]; 142 | tmp[6] = -a[0] * a[6] * a[15] + a[0] * a[7] * a[14] + a[4] * a[2] * a[15] - a[4] * a[3] * a[14] - a[12] * a[2] * a[7] + a[12] * a[3] * a[6]; 143 | tmp[7] = a[0] * a[6] * a[11] - a[0] * a[7] * a[10] - a[4] * a[2] * a[11] + a[4] * a[3] * a[10] + a[8] * a[2] * a[7] - a[8] * a[3] * a[6]; 144 | tmp[8] = a[4] * a[9] * a[15] - a[4] * a[11] * a[13] - a[8] * a[5] * a[15] + a[8] * a[7] * a[13] + a[12] * a[5] * a[11] - a[12] * a[7] * a[9]; 145 | tmp[9] = -a[0] * a[9] * a[15] + a[0] * a[11] * a[13] + a[8] * a[1] * a[15] - a[8] * a[3] * a[13] - a[12] * a[1] * a[11] + a[12] * a[3] * a[9]; 146 | tmp[10] = a[0] * a[5] * a[15] - a[0] * a[7] * a[13] - a[4] * a[1] * a[15] + a[4] * a[3] * a[13] + a[12] * a[1] * a[7] - a[12] * a[3] * a[5]; 147 | tmp[11] = -a[0] * a[5] * a[11] + a[0] * a[7] * a[9] + a[4] * a[1] * a[11] - a[4] * a[3] * a[9] - a[8] * a[1] * a[7] + a[8] * a[3] * a[5]; 148 | tmp[12] = -a[4] * a[9] * a[14] + a[4] * a[10] * a[13] + a[8] * a[5] * a[14] - a[8] * a[6] * a[13] - a[12] * a[5] * a[10] + a[12] * a[6] * a[9]; 149 | tmp[13] = a[0] * a[9] * a[14] - a[0] * a[10] * a[13] - a[8] * a[1] * a[14] + a[8] * a[2] * a[13] + a[12] * a[1] * a[10] - a[12] * a[2] * a[9]; 150 | tmp[14] = -a[0] * a[5] * a[14] + a[0] * a[6] * a[13] + a[4] * a[1] * a[14] - a[4] * a[2] * a[13] - a[12] * a[1] * a[6] + a[12] * a[2] * a[5]; 151 | tmp[15] = a[0] * a[5] * a[10] - a[0] * a[6] * a[9] - a[4] * a[1] * a[10] + a[4] * a[2] * a[9] + a[8] * a[1] * a[6] - a[8] * a[2] * a[5]; 152 | 153 | float det = a[0] * tmp[0] + a[1] * tmp[4] + a[2] * tmp[8] + a[3] * tmp[12]; 154 | if (det == 0.0f) { 155 | for (int i = 0; i < 16; i++) { 156 | out[i] = a[i]; 157 | } 158 | } 159 | det = 1.0f / det; 160 | 161 | for (int i = 0; i < 16; i++) { 162 | out[i] = tmp[i] * det; 163 | } 164 | } 165 | 166 | float clamp(float v, float low, float high) { 167 | return fmaxf(fminf(v, high), low); 168 | } 169 | -------------------------------------------------------------------------------- /tfx.zig: -------------------------------------------------------------------------------- 1 | // this wrapper is incomplete, and may yet be awful to use! 2 | // for anything not exposed with a convenience wrappt can be accessed via raw.* 3 | pub const raw = @cImport({ 4 | @cInclude("tinyfx.h"); 5 | }); 6 | const std = @import("std"); 7 | 8 | pub const Debug = struct { 9 | pub const print = raw.tfx_debug_print; 10 | pub const blit_rgba = raw.tfx_debug_blit_rgba; 11 | pub const blit_pal = raw.tfx_debug_blit_pal; 12 | pub const set_palette = raw.tfx_debug_set_palette; 13 | }; 14 | pub const UniformType = enum { 15 | Int, 16 | Vec4, 17 | Mat4, 18 | }; 19 | pub const Program = struct { 20 | handle: raw.tfx_program, 21 | pub inline fn create(src: []const u8, attribs: [*][]const u8, attrib_count: usize) anyerror!Program { 22 | // alternative... 23 | // pub inline fn create(src: []const u8, attribs: [*c][*c]const u8) anyerror!Program { 24 | // var attribs: [*c][*c]const u8 = &[_:null]?[*:0]const u8 { 25 | // "a_position", 26 | // null 27 | // }; 28 | // var handle = raw.tfx_program_len_new(src.ptr, @intCast(c_int, src.len), src.ptr, @intCast(c_int, src.len), attribs, -1) 29 | var handle = raw.tfx_program_len_new(src.ptr, @intCast(c_int, src.len), src.ptr, @intCast(c_int, src.len), @ptrCast([*c][*c]const u8, attribs), @intCast(c_int, attrib_count)); 30 | if (handle == 0) { 31 | return error.ShaderCompileFailure; 32 | } 33 | return Program{ .handle = handle }; 34 | } 35 | }; 36 | pub const Uniform = struct { 37 | handle: raw.tfx_uniform, 38 | pub inline fn create(name: [:0]const u8, utype: UniformType, count: i32) Uniform { 39 | const real_type = switch (utype) { 40 | .Int => @intToEnum(raw.tfx_uniform_type, raw.TFX_UNIFORM_INT), 41 | .Vec4 => @intToEnum(raw.tfx_uniform_type, raw.TFX_UNIFORM_VEC4), 42 | .Mat4 => @intToEnum(raw.tfx_uniform_type, raw.TFX_UNIFORM_MAT4), 43 | }; 44 | var handle = raw.tfx_uniform_new(name, real_type, @intCast(c_int, count)); 45 | return .{ .handle = handle }; 46 | } 47 | }; 48 | pub const ComponentType = enum { 49 | Float, 50 | }; 51 | pub const VertexFormat = struct { 52 | format: raw.tfx_vertex_format, 53 | pub inline fn start() VertexFormat { 54 | return VertexFormat{ .format = raw.tfx_vertex_format_start() }; 55 | } 56 | pub inline fn add(self: *VertexFormat, slot: u8, count: usize, normalized: bool, component: ComponentType) void { 57 | var real_type = switch (component) { 58 | .Float => @intToEnum(raw.tfx_component_type, raw.TFX_TYPE_FLOAT), 59 | }; 60 | raw.tfx_vertex_format_add(&self.format, slot, count, normalized, real_type); 61 | } 62 | pub inline fn end(self: *VertexFormat) void { 63 | raw.tfx_vertex_format_end(&self.format); 64 | } 65 | }; 66 | pub const Buffer = raw.tfx_buffer; 67 | pub fn TransientBuffer(t: var) type { 68 | return struct { 69 | ptr: [*]align(1) t, 70 | handle: raw.tfx_transient_buffer, 71 | pub fn create(fmt: *VertexFormat, count: u16) TransientBuffer(t) { 72 | var tb = raw.tfx_transient_buffer_new(&fmt.format, count); 73 | return TransientBuffer(t){ 74 | .handle = tb, 75 | .ptr = @ptrCast([*]align(1) t, tb.data), 76 | }; 77 | } 78 | }; 79 | } 80 | // var tb = tfx.TransientBuffer(f32).create(&fmt, 3); 81 | // tb.ptr[0] = -1.0; 82 | // tb.ptr[3] = -1.0; 83 | // tb.ptr[4] = -1.0; 84 | // tb.ptr[7] = -1.0; 85 | // tb.ptr[2] = depth; 86 | // tb.ptr[5] = depth; 87 | // tb.ptr[8] = depth; 88 | // tb.ptr[1] = 3.0; 89 | // tb.ptr[6] = 3.0; 90 | pub const ResetFlags = struct { 91 | pub const None = raw.TFX_RESET_NONE; 92 | pub const DebugOverlay = raw.TFX_RESET_DEBUG_OVERLAY; 93 | pub const DebugOverlayStats = raw.TFX_RESET_DEBUG_OVERLAY_STATS; 94 | pub const ReportGPUTimings = raw.TFX_RESET_REPORT_GPU_TIMINGS; 95 | }; 96 | pub const State = struct { 97 | pub const Default = raw.TFX_STATE_DEFAULT; 98 | pub const RGBWrite = raw.TFX_STATE_RGB_WRITE; 99 | pub const DepthWrite = raw.TFX_STATE_DEPTH_WRITE; 100 | }; 101 | pub const TextureFlags = struct { 102 | pub const FilterPoint = raw.TFX_TEXTURE_FILTER_POINT; 103 | pub const FilterLinear = raw.TFX_TEXTURE_FILTER_LINEAR; 104 | pub const CPUWritable = raw.TFX_TEXTURE_CPU_WRITABLE; 105 | pub const GenMips = raw.TFX_TEXTURE_GEN_MIPS; 106 | pub const ReserveMips = raw.TFX_TEXTURE_RESERVE_MIPS; 107 | pub const Cube = raw.TFX_TEXTURE_CUBE; 108 | pub const MSAASample = raw.TFX_TEXTURE_MSAA_SAMPLE; 109 | pub const MSAAX2 = raw.TFX_TEXTURE_MSAA_X2; 110 | pub const MSAAX4 = raw.TFX_TEXTURE_MSAA_X4; 111 | pub const External = raw.TFX_TEXTURE_EXTERNAL; 112 | }; 113 | pub const TextureFormat = struct { 114 | pub const RGB565 = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_RGB565); 115 | pub const RGBA8 = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_RGBA8); 116 | pub const SRGB8 = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_SRGB8); 117 | pub const SRGB8_A8 = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_SRGB8_A8); 118 | pub const RGB10A2 = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_RGB10A2); 119 | pub const RG11B10F = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_RG11B10F); 120 | pub const RGB16F = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_RGB16F); 121 | pub const RGBA16F = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_RGBA16F); 122 | pub const RGB565_D16 = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_RGB565_D16); 123 | pub const RGBA8_D16 = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_RGBA8_D16); 124 | pub const RGBA8_D24 = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_RGBA8_D24); 125 | pub const R16F = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_R16F); 126 | pub const R32UI = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_R32UI); 127 | pub const R32F = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_R32F); 128 | pub const RG16F = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_RG16F); 129 | pub const RG32F = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_RG32F); 130 | pub const D16 = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_D16); 131 | pub const D24 = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_D24); 132 | pub const D32 = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_D32); 133 | pub const D32F = @intToEnum(raw.tfx_format, raw.TFX_FORMAT_D32F); 134 | }; 135 | pub const PlatformData = struct { 136 | pub inline fn create(gl_version: c_int, gl_get_proc_address: var) raw.tfx_platform_data { 137 | var pd = std.mem.zeroes(raw.tfx_platform_data); 138 | pd.context_version = gl_version; 139 | pd.gl_get_proc_address = gl_get_proc_address; 140 | return pd; 141 | } 142 | }; 143 | pub const ViewFlags = struct { 144 | pub const None = raw.TFX_VIEW_NONE; 145 | pub const Invalidate = raw.TFX_VIEW_INVALIDATE; 146 | pub const Flush = raw.TFX_VIEW_FLUSH; 147 | pub const SortSequential = raw.TFX_VIEW_SORT_SEQUENTIAL; 148 | pub const Default = raw.TFX_VIEW_DEFAULT; 149 | }; 150 | pub const View = struct { 151 | id: u8, 152 | pub inline fn setName(self: *const View, name: [:0]const u8) void { 153 | raw.tfx_view_set_name(self.id, name); 154 | } 155 | pub inline fn setFlags(self: *const View, flags: c_int) void { 156 | raw.tfx_view_set_flags(self.id, @intToEnum(raw.tfx_view_flags, flags)); 157 | } 158 | pub inline fn setClearColor(self: *const View, color: u32) void { 159 | raw.tfx_view_set_clear_color(self.id, color); 160 | } 161 | pub inline fn setClearDepth(self: *const View, depth: f32) void { 162 | raw.tfx_view_set_clear_depth(self.id, depth); 163 | } 164 | pub inline fn setCanvas(self: *const View, canvas: *raw.tfx_canvas, layer: i32) void { 165 | raw.tfx_view_set_canvas(self.id, canvas, @intCast(c_int, layer)); 166 | } 167 | pub inline fn setViewports(self: *const View, count: usize, viewports: [*][*]u16) void { 168 | raw.tfx_view_set_viewports(self.id, @intCast(c_int, count), @ptrCast([*]?[*]u16, viewports)); 169 | } 170 | }; 171 | 172 | pub inline fn setPlatformData(pd: raw.tfx_platform_data) void { 173 | raw.tfx_set_platform_data(pd); 174 | } 175 | pub inline fn reset(w: u32, h: u32, flags: c_int) void { 176 | raw.tfx_reset(@intCast(u16, w), @intCast(u16, h), @intToEnum(raw.tfx_reset_flags, flags)); 177 | } 178 | pub const shutdown = raw.tfx_shutdown; 179 | pub const setBuffer = raw.tfx_set_buffer; 180 | pub inline fn setTransientBuffer(tb: var) void { 181 | raw.tfx_set_transient_buffer(tb.handle); 182 | } 183 | pub inline fn setTexture(uniform: *Uniform, tex: *raw.tfx_texture, slot: u8) void { 184 | raw.tfx_set_texture(&uniform.handle, tex, slot); 185 | } 186 | pub const setState = raw.tfx_set_state; 187 | pub const setCallback = raw.tfx_set_callback; 188 | pub inline fn setUniform(uniform: *Uniform, data: [*]f32, count: i32) void { 189 | raw.tfx_set_uniform(&uniform.handle, data, @intCast(c_int, count)); 190 | } 191 | pub inline fn submit(view: View, program: Program, retain: bool) void { 192 | return raw.tfx_submit(view.id, program.handle, retain); 193 | } 194 | pub inline fn touch(view: View) void { 195 | return raw.tfx_touch(view.id); 196 | } 197 | pub inline fn getView(viewid: u8) View { 198 | return .{ .id = viewid }; 199 | } 200 | pub inline fn frame() raw.tfx_stats { 201 | return raw.tfx_frame(); 202 | } 203 | -------------------------------------------------------------------------------- /tinyfx.c: -------------------------------------------------------------------------------- 1 | #define TFX_IMPLEMENTATION 2 | #if !defined(TFX_DEBUG) && defined(_DEBUG) 3 | // enable tfx debug in msvc debug configurations. 4 | #define TFX_DEBUG 5 | #endif 6 | #include "tinyfx.h" 7 | 8 | #ifdef TFX_LEAK_CHECK 9 | #define STB_LEAKCHECK_IMPLEMENTATION 10 | #include "stb_leakcheck.h" 11 | #endif 12 | 13 | /**********************\ 14 | | implementation stuff | 15 | \**********************/ 16 | #ifdef TFX_IMPLEMENTATION 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | // TODO: look into just keeping the stuff from GL header in here, this thing 23 | // isn't included on many systems and is kind of annoying to always need. 24 | #include 25 | //#ifdef __ANDROID__ 26 | //#include 27 | //#endif 28 | #include 29 | #include 30 | #include 31 | #include 32 | #ifdef TFX_DEBUG 33 | #include 34 | #else 35 | #define assert(op) (void)(op); 36 | #endif 37 | 38 | // needed on msvc 39 | #if defined(_MSC_VER) && !defined(snprintf) 40 | #define snprintf _snprintf 41 | #endif 42 | 43 | #ifdef _MSC_VER 44 | #pragma warning( push ) 45 | #pragma warning( disable : 4996 ) 46 | #endif 47 | 48 | #ifndef TFX_TRANSIENT_BUFFER_COUNT 49 | #define TFX_TRANSIENT_BUFFER_COUNT 3 50 | #endif 51 | 52 | #ifndef TFX_UNIFORM_BUFFER_SIZE 53 | // by default, allow up to 4MB of uniform updates per frame. 54 | #define TFX_UNIFORM_BUFFER_SIZE 1024*1024*4 55 | #endif 56 | 57 | #ifndef TFX_TRANSIENT_BUFFER_SIZE 58 | // by default, allow up to 4MB of transient buffer data per frame. 59 | #define TFX_TRANSIENT_BUFFER_SIZE 1024*1024*4 60 | #endif 61 | 62 | // The following code is public domain, from https://github.com/nothings/stb 63 | ////////////////////////////////////////////////////////////////////////////// 64 | #ifdef __cplusplus 65 | #define STB_STRETCHY_BUFFER_CPP 66 | #endif 67 | #ifndef STB_STRETCHY_BUFFER_H_INCLUDED 68 | #define STB_STRETCHY_BUFFER_H_INCLUDED 69 | 70 | #ifndef NO_STRETCHY_BUFFER_SHORT_NAMES 71 | #define sb_free stb_sb_free 72 | #define sb_push stb_sb_push 73 | #define sb_count stb_sb_count 74 | #define sb_add stb_sb_add 75 | #define sb_last stb_sb_last 76 | #endif 77 | 78 | #ifdef STB_STRETCHY_BUFFER_CPP 79 | #define stb_sb_push(t,a,v) (stb__sbmaybegrow(t,a,1), (a)[stb__sbn(a)++] = (v)) 80 | #define stb_sb_add(t,a,n) (stb__sbmaybegrow(t,a,n), stb__sbn(a)+=(n), &(a)[stb__sbn(a)-(n)]) 81 | #define stb__sbmaybegrow(t,a,n) (stb__sbneedgrow(a,(n)) ? stb__sbgrow(t,a,n) : 0) 82 | #define stb__sbgrow(t,a,n) ((a) = (t*)stb__sbgrowf((void*)(a), (n), sizeof(t))) 83 | #else 84 | #define stb_sb_push(a,v) (stb__sbmaybegrow(a,1), (a)[stb__sbn(a)++] = (v)) 85 | #define stb_sb_add(a,n) (stb__sbmaybegrow(a,n), stb__sbn(a)+=(n), &(a)[stb__sbn(a)-(n)]) 86 | #define stb__sbmaybegrow(a,n) (stb__sbneedgrow(a,(n)) ? stb__sbgrow(a,n) : 0) 87 | #define stb__sbgrow(a,n) ((a) = stb__sbgrowf((a), (n), sizeof(*(a)))) 88 | #endif 89 | 90 | #define stb_sb_free(a) ((a) ? free(stb__sbraw(a)),0 : 0) 91 | #define stb_sb_count(a) ((a) ? stb__sbn(a) : 0) 92 | #define stb_sb_last(a) ((a)[stb__sbn(a)-1]) 93 | 94 | #define stb__sbraw(a) ((int *) (a) - 2) 95 | #define stb__sbm(a) stb__sbraw(a)[0] 96 | #define stb__sbn(a) stb__sbraw(a)[1] 97 | 98 | #define stb__sbneedgrow(a,n) ((a)==0 || stb__sbn(a)+(n) >= stb__sbm(a)) 99 | 100 | #include 101 | 102 | static void * stb__sbgrowf(void *arr, int increment, int itemsize) 103 | { 104 | int dbl_cur = arr ? 2 * stb__sbm(arr) : 0; 105 | int min_needed = stb_sb_count(arr) + increment; 106 | int m = dbl_cur > min_needed ? dbl_cur : min_needed; 107 | int *p = (int *)realloc(arr ? stb__sbraw(arr) : 0, itemsize * m + sizeof(int) * 2); 108 | if (p) { 109 | if (!arr) 110 | p[1] = 0; 111 | p[0] = m; 112 | return p + 2; 113 | } 114 | else { 115 | #ifdef STRETCHY_BUFFER_OUT_OF_MEMORY 116 | STRETCHY_BUFFER_OUT_OF_MEMORY; 117 | #endif 118 | return (void *)(2 * sizeof(int)); // try to force a NULL pointer exception later 119 | } 120 | } 121 | #endif // STB_STRETCHY_BUFFER_H_INCLUDED 122 | ////////////////////////////////////////////////////////////////////////////// 123 | 124 | static char *tfx_strdup(const char *src) { 125 | size_t len = strlen(src) + 1; 126 | char *s = malloc(len); 127 | if (s == NULL) 128 | return NULL; 129 | // copy len includes the \0 terminator 130 | return (char *)memcpy(s, src, len); 131 | } 132 | 133 | #ifdef TFX_DEBUG 134 | //#define TFX_FATAL_ERRORS 135 | # ifdef TFX_FATAL_ERRORS 136 | # define CHECK(fn) fn; { GLenum _status; while ((_status = tfx_glGetError())) { if (_status == GL_NO_ERROR) break; TFX_ERROR("%s:%d GL ERROR: %d", __FILE__, __LINE__, _status); assert(0); } } 137 | # else 138 | # define CHECK(fn) fn; { GLenum _status; while ((_status = tfx_glGetError())) { if (_status == GL_NO_ERROR) break; TFX_ERROR("%s:%d GL ERROR: %d", __FILE__, __LINE__, _status); } } 139 | # endif 140 | #else 141 | # define CHECK(fn) fn; 142 | #endif 143 | 144 | #define TFX_INFO(msg, ...) tfx_printf(TFX_SEVERITY_INFO, msg, __VA_ARGS__) 145 | #define TFX_WARN(msg, ...) tfx_printf(TFX_SEVERITY_WARNING, msg, __VA_ARGS__) 146 | #define TFX_ERROR(msg, ...) tfx_printf(TFX_SEVERITY_ERROR, msg, __VA_ARGS__) 147 | #define TFX_FATAL(msg, ...) tfx_printf(TFX_SEVERITY_FATAL, msg, __VA_ARGS__) 148 | 149 | #define VIEW_MAX 256 150 | #define TIMER_LATENCY 3 151 | #define TIMER_COUNT ((VIEW_MAX+1)*TIMER_LATENCY) 152 | 153 | // view flags 154 | enum { 155 | // clear modes 156 | TFXI_VIEW_CLEAR_COLOR = 1 << 0, 157 | TFXI_VIEW_CLEAR_DEPTH = 1 << 1, 158 | 159 | // depth modes 160 | TFXI_VIEW_DEPTH_TEST_LT = 1 << 2, 161 | TFXI_VIEW_DEPTH_TEST_GT = 1 << 3, 162 | TFXI_VIEW_DEPTH_TEST_EQ = 1 << 4, 163 | 164 | // scissor test 165 | TFXI_VIEW_SCISSOR = 1 << 5, 166 | 167 | TFXI_VIEW_INVALIDATE = 1 << 6, 168 | TFXI_VIEW_FLUSH = 1 << 7, 169 | TFXI_VIEW_SORT_SEQUENTIAL = 1 << 8 170 | }; 171 | 172 | typedef struct tfx_rect { 173 | uint16_t x; 174 | uint16_t y; 175 | uint16_t w; 176 | uint16_t h; 177 | } tfx_rect; 178 | 179 | typedef struct tfx_blit_op { 180 | tfx_canvas *source; 181 | int source_mip; 182 | tfx_rect rect; 183 | GLenum mask; 184 | } tfx_blit_op; 185 | 186 | typedef struct tfx_draw { 187 | tfx_draw_callback callback; 188 | uint64_t flags; 189 | 190 | tfx_program program; 191 | tfx_uniform *uniforms; 192 | 193 | tfx_texture textures[8]; 194 | uint8_t textures_mip[8]; 195 | bool textures_write[8]; 196 | tfx_buffer ssbos[8]; 197 | bool ssbo_write[8]; 198 | tfx_buffer vbo; 199 | bool use_vbo; 200 | 201 | tfx_buffer ibo; 202 | bool use_ibo; 203 | 204 | tfx_vertex_format tvb_fmt; 205 | bool use_tvb; 206 | 207 | tfx_rect scissor_rect; 208 | bool use_scissor; 209 | 210 | size_t offset; 211 | uint32_t indices; 212 | uint32_t depth; 213 | 214 | // for compute jobs 215 | uint32_t threads_x; 216 | uint32_t threads_y; 217 | uint32_t threads_z; 218 | } tfx_draw; 219 | 220 | typedef struct tfx_view { 221 | uint32_t flags; 222 | 223 | const char *name; 224 | 225 | bool has_canvas; 226 | tfx_canvas canvas; 227 | int canvas_layer; 228 | 229 | tfx_draw *draws; 230 | tfx_draw *jobs; 231 | tfx_blit_op *blits; 232 | 233 | unsigned clear_color; 234 | float clear_depth; 235 | 236 | tfx_rect scissor_rect; 237 | // https://opengl.gpuinfo.org/displaycapability.php?name=GL_MAX_VIEWPORTS 238 | tfx_rect viewports[16]; 239 | int viewport_count; 240 | 241 | // for single pass rendering to eyes, cubes, shadowmaps etc. 242 | int instance_mul; 243 | 244 | float view[16]; 245 | float proj_left[16]; 246 | float proj_right[16]; 247 | } tfx_view; 248 | 249 | #define TFXI_VIEW_CLEAR_MASK (TFXI_VIEW_CLEAR_COLOR | TFXI_VIEW_CLEAR_DEPTH) 250 | #define TFXI_VIEW_DEPTH_TEST_MASK (TFXI_VIEW_DEPTH_TEST_LT | TFXI_VIEW_DEPTH_TEST_GT | TFXI_VIEW_DEPTH_TEST_EQ) 251 | 252 | #define TFXI_STATE_CULL_MASK (TFX_STATE_CULL_CW | TFX_STATE_CULL_CCW) 253 | #define TFXI_STATE_BLEND_MASK (TFX_STATE_BLEND_ALPHA) 254 | #define TFXI_STATE_DRAW_MASK (TFX_STATE_DRAW_POINTS \ 255 | | TFX_STATE_DRAW_LINES | TFX_STATE_DRAW_LINE_STRIP | TFX_STATE_DRAW_LINE_LOOP \ 256 | | TFX_STATE_DRAW_TRI_STRIP | TFX_STATE_DRAW_TRI_FAN \ 257 | ) 258 | 259 | static tfx_canvas g_backbuffer; 260 | static tfx_timing_info last_timings[VIEW_MAX]; 261 | static tfx_platform_data g_platform_data; 262 | 263 | typedef struct tfx_glext { 264 | const char *ext; 265 | bool supported; 266 | } tfx_glext; 267 | 268 | static tfx_glext available_exts[] = { 269 | { "GL_ARB_multisample", false }, 270 | { "GL_ARB_compute_shader", false }, 271 | { "GL_ARB_texture_float", false }, 272 | { "GL_EXT_debug_marker", false }, 273 | { "GL_ARB_debug_output", false }, 274 | { "GL_KHR_debug", false }, 275 | { "GL_NVX_gpu_memory_info", false }, 276 | // guaranteed by desktop GL 3.3+ or GLES 3.0+ 277 | { "GL_ARB_instanced_arrays", false }, 278 | { "GL_ARB_seamless_cube_map", false }, 279 | { "GL_EXT_texture_filter_anisotropic", false }, 280 | { "GL_ARB_multi_bind", false }, 281 | // TODO 282 | // GL_AMD_vertex_shader_layer 283 | // GL_AMD_vertex_shader_viewport_index 284 | // GL_ARB_multi_bind 285 | // GL_ARB_multi_draw_indirect 286 | // GL_QCOM_texture_foveated 287 | // GL_OVR_multiview2 288 | // GL_OVR_multiview_multisampled_render_to_texture 289 | // GL_OES_element_index_uint 290 | // GL_OES_depth24 291 | // GL_OES_depth_texture 292 | // GL_OES_depth_texture_cube_map 293 | // GL_EXT_sRGB 294 | // GL_EXT_multisampled_render_to_texture 295 | // GL_EXT_disjoint_timer_query 296 | // GL_EXT_clip_control 297 | // GL_EXT_clip_cull_distance 298 | // GL_EXT_color_buffer_float 299 | // GL_EXT_color_buffer_half_float 300 | { NULL, false } 301 | }; 302 | 303 | PFNGLFLUSHPROC tfx_glFlush; 304 | PFNGLGETSTRINGPROC tfx_glGetString; 305 | PFNGLGETSTRINGIPROC tfx_glGetStringi; 306 | PFNGLGETERRORPROC tfx_glGetError; 307 | PFNGLBLENDFUNCPROC tfx_glBlendFunc; 308 | PFNGLCOLORMASKPROC tfx_glColorMask; 309 | PFNGLGETINTEGERVPROC tfx_glGetIntegerv; 310 | PFNGLGETFLOATVPROC tfx_glGetFloatv; 311 | PFNGLGENQUERIESPROC tfx_glGenQueries; 312 | PFNGLDELETEQUERIESPROC tfx_glDeleteQueries; 313 | PFNGLBEGINQUERYPROC tfx_glBeginQuery; 314 | PFNGLENDQUERYPROC tfx_glEndQuery; 315 | PFNGLQUERYCOUNTERPROC tfx_glQueryCounter; 316 | PFNGLGETQUERYOBJECTUIVPROC tfx_glGetQueryObjectuiv; 317 | PFNGLGETQUERYOBJECTUI64VPROC tfx_glGetQueryObjectui64v; 318 | PFNGLGENBUFFERSPROC tfx_glGenBuffers; 319 | PFNGLBINDBUFFERPROC tfx_glBindBuffer; 320 | PFNGLBUFFERDATAPROC tfx_glBufferData; 321 | PFNGLBUFFERSTORAGEPROC tfx_glBufferStorage; 322 | PFNGLDELETEBUFFERSPROC tfx_glDeleteBuffers; 323 | PFNGLDELETETEXTURESPROC tfx_glDeleteTextures; 324 | PFNGLCREATESHADERPROC tfx_glCreateShader; 325 | PFNGLSHADERSOURCEPROC tfx_glShaderSource; 326 | PFNGLCOMPILESHADERPROC tfx_glCompileShader; 327 | PFNGLGETSHADERIVPROC tfx_glGetShaderiv; 328 | PFNGLGETSHADERINFOLOGPROC tfx_glGetShaderInfoLog; 329 | PFNGLDELETESHADERPROC tfx_glDeleteShader; 330 | PFNGLCREATEPROGRAMPROC tfx_glCreateProgram; 331 | PFNGLATTACHSHADERPROC tfx_glAttachShader; 332 | PFNGLBINDATTRIBLOCATIONPROC tfx_glBindAttribLocation; 333 | PFNGLLINKPROGRAMPROC tfx_glLinkProgram; 334 | PFNGLGETPROGRAMIVPROC tfx_glGetProgramiv; 335 | PFNGLGETPROGRAMINFOLOGPROC tfx_glGetProgramInfoLog; 336 | PFNGLDELETEPROGRAMPROC tfx_glDeleteProgram; 337 | PFNGLGENTEXTURESPROC tfx_glGenTextures; 338 | PFNGLBINDTEXTUREPROC tfx_glBindTexture; 339 | PFNGLBINDTEXTURESPROC tfx_glBindTextures; 340 | PFNGLTEXPARAMETERIPROC tfx_glTexParameteri; 341 | PFNGLTEXPARAMETERIVPROC tfx_glTexParameteriv; 342 | PFNGLTEXPARAMETERFPROC tfx_glTexParameterf; 343 | PFNGLPIXELSTOREIPROC tfx_glPixelStorei; 344 | PFNGLTEXIMAGE2DPROC tfx_glTexImage2D; 345 | PFNGLTEXIMAGE3DPROC tfx_glTexImage3D; 346 | PFNGLTEXIMAGE2DMULTISAMPLEPROC tfx_glTexImage2DMultisample; 347 | PFNGLTEXSTORAGE2DPROC tfx_glTexStorage2D; 348 | PFNGLTEXSTORAGE3DPROC tfx_glTexStorage3D; 349 | PFNGLTEXSTORAGE2DMULTISAMPLEPROC tfx_glTexStorage2DMultisample; 350 | PFNGLTEXSUBIMAGE2DPROC tfx_glTexSubImage2D; 351 | PFNGLTEXSUBIMAGE3DPROC tfx_glTexSubImage3D; 352 | PFNGLINVALIDATETEXSUBIMAGEPROC tfx_glInvalidateTexSubImage; 353 | PFNGLGENERATEMIPMAPPROC tfx_glGenerateMipmap; 354 | PFNGLGENFRAMEBUFFERSPROC tfx_glGenFramebuffers; 355 | PFNGLDELETEFRAMEBUFFERSPROC tfx_glDeleteFramebuffers; 356 | PFNGLBINDFRAMEBUFFERPROC tfx_glBindFramebuffer; 357 | PFNGLBLITFRAMEBUFFERPROC tfx_glBlitFramebuffer; 358 | PFNGLCOPYIMAGESUBDATAPROC tfx_glCopyImageSubData; 359 | PFNGLFRAMEBUFFERTEXTURE2DPROC tfx_glFramebufferTexture2D; 360 | PFNGLINVALIDATEFRAMEBUFFERPROC tfx_glInvalidateFramebuffer; 361 | PFNGLGENRENDERBUFFERSPROC tfx_glGenRenderbuffers; 362 | PFNGLBINDRENDERBUFFERPROC tfx_glBindRenderbuffer; 363 | PFNGLRENDERBUFFERSTORAGEPROC tfx_glRenderbufferStorage; 364 | PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC tfx_glRenderbufferStorageMultisample; 365 | PFNGLFRAMEBUFFERRENDERBUFFERPROC tfx_glFramebufferRenderbuffer; 366 | PFNGLFRAMEBUFFERTEXTUREPROC tfx_glFramebufferTexture; 367 | PFNGLDRAWBUFFERSPROC tfx_glDrawBuffers; 368 | PFNGLREADBUFFERPROC tfx_glReadBuffer; 369 | PFNGLCHECKFRAMEBUFFERSTATUSPROC tfx_glCheckFramebufferStatus; 370 | PFNGLGETUNIFORMLOCATIONPROC tfx_glGetUniformLocation; 371 | PFNGLRELEASESHADERCOMPILERPROC tfx_glReleaseShaderCompiler; 372 | PFNGLGENVERTEXARRAYSPROC tfx_glGenVertexArrays; 373 | PFNGLBINDVERTEXARRAYPROC tfx_glBindVertexArray; 374 | PFNGLMAPBUFFERRANGEPROC tfx_glMapBufferRange; 375 | PFNGLBUFFERSUBDATAPROC tfx_glBufferSubData; 376 | PFNGLUNMAPBUFFERPROC tfx_glUnmapBuffer; 377 | PFNGLUSEPROGRAMPROC tfx_glUseProgram; 378 | PFNGLMEMORYBARRIERPROC tfx_glMemoryBarrier; 379 | PFNGLBINDBUFFERBASEPROC tfx_glBindBufferBase; 380 | PFNGLDISPATCHCOMPUTEPROC tfx_glDispatchCompute; 381 | PFNGLVIEWPORTPROC tfx_glViewport; 382 | PFNGLVIEWPORTINDEXEDFPROC tfx_glViewportIndexedf; 383 | PFNGLSCISSORPROC tfx_glScissor; 384 | PFNGLCLEARCOLORPROC tfx_glClearColor; 385 | PFNGLCLEARDEPTHFPROC tfx_glClearDepthf; 386 | PFNGLCLEARPROC tfx_glClear; 387 | PFNGLENABLEPROC tfx_glEnable; 388 | PFNGLDEPTHFUNCPROC tfx_glDepthFunc; 389 | PFNGLDISABLEPROC tfx_glDisable; 390 | PFNGLDEPTHMASKPROC tfx_glDepthMask; 391 | PFNGLFRONTFACEPROC tfx_glFrontFace; 392 | PFNGLPOLYGONMODEPROC tfx_glPolygonMode; 393 | PFNGLUNIFORM1IVPROC tfx_glUniform1iv; 394 | PFNGLUNIFORM1FVPROC tfx_glUniform1fv; 395 | PFNGLUNIFORM2FVPROC tfx_glUniform2fv; 396 | PFNGLUNIFORM3FVPROC tfx_glUniform3fv; 397 | PFNGLUNIFORM4FVPROC tfx_glUniform4fv; 398 | PFNGLUNIFORMMATRIX2FVPROC tfx_glUniformMatrix2fv; 399 | PFNGLUNIFORMMATRIX3FVPROC tfx_glUniformMatrix3fv; 400 | PFNGLUNIFORMMATRIX4FVPROC tfx_glUniformMatrix4fv; 401 | PFNGLENABLEVERTEXATTRIBARRAYPROC tfx_glEnableVertexAttribArray; 402 | PFNGLVERTEXATTRIBPOINTERPROC tfx_glVertexAttribPointer; 403 | PFNGLDISABLEVERTEXATTRIBARRAYPROC tfx_glDisableVertexAttribArray; 404 | PFNGLACTIVETEXTUREPROC tfx_glActiveTexture; 405 | PFNGLDRAWELEMENTSINSTANCEDPROC tfx_glDrawElementsInstanced; 406 | PFNGLDRAWARRAYSINSTANCEDPROC tfx_glDrawArraysInstanced; 407 | PFNGLDRAWELEMENTSPROC tfx_glDrawElements; 408 | PFNGLDRAWARRAYSPROC tfx_glDrawArrays; 409 | PFNGLDELETEVERTEXARRAYSPROC tfx_glDeleteVertexArrays; 410 | PFNGLBINDFRAGDATALOCATIONPROC tfx_glBindFragDataLocation; 411 | 412 | // debug output/markers 413 | PFNGLPUSHDEBUGGROUPPROC tfx_glPushDebugGroup; 414 | PFNGLPOPDEBUGGROUPPROC tfx_glPopDebugGroup; 415 | PFNGLINSERTEVENTMARKEREXTPROC tfx_glInsertEventMarkerEXT; 416 | 417 | void load_em_up(void* (*get_proc_address)(const char*)) { 418 | tfx_glFlush = get_proc_address("glFlush"); 419 | tfx_glGetString = get_proc_address("glGetString"); 420 | tfx_glGetStringi = get_proc_address("glGetStringi"); 421 | tfx_glGetError = get_proc_address("glGetError"); 422 | tfx_glBlendFunc = get_proc_address("glBlendFunc"); 423 | tfx_glColorMask = get_proc_address("glColorMask"); 424 | tfx_glGetIntegerv = get_proc_address("glGetIntegerv"); 425 | tfx_glGetFloatv = get_proc_address("glGetFloatv"); 426 | tfx_glGenQueries = get_proc_address("glGenQueries"); 427 | tfx_glDeleteQueries = get_proc_address("glDeleteQueries"); 428 | tfx_glBeginQuery = get_proc_address("glBeginQuery"); 429 | tfx_glEndQuery = get_proc_address("glEndQuery"); 430 | tfx_glQueryCounter = get_proc_address("glQueryCounter"); 431 | tfx_glGetQueryObjectuiv = get_proc_address("glGetQueryObjectuiv"); 432 | tfx_glGetQueryObjectui64v = get_proc_address("glGetQueryObjectui64v"); 433 | tfx_glGenBuffers = get_proc_address("glGenBuffers"); 434 | tfx_glBindBuffer = get_proc_address("glBindBuffer"); 435 | tfx_glBufferData = get_proc_address("glBufferData"); 436 | tfx_glBufferStorage = get_proc_address("glBufferStorage"); 437 | tfx_glDeleteBuffers = get_proc_address("glDeleteBuffers"); 438 | tfx_glDeleteTextures= get_proc_address("glDeleteTextures"); 439 | tfx_glCreateShader = get_proc_address("glCreateShader"); 440 | tfx_glShaderSource = get_proc_address("glShaderSource"); 441 | tfx_glCompileShader = get_proc_address("glCompileShader"); 442 | tfx_glGetShaderiv = get_proc_address("glGetShaderiv"); 443 | tfx_glGetShaderInfoLog = get_proc_address("glGetShaderInfoLog"); 444 | tfx_glDeleteShader = get_proc_address("glDeleteShader"); 445 | tfx_glCreateProgram = get_proc_address("glCreateProgram"); 446 | tfx_glAttachShader = get_proc_address("glAttachShader"); 447 | tfx_glBindAttribLocation = get_proc_address("glBindAttribLocation"); 448 | tfx_glLinkProgram = get_proc_address("glLinkProgram"); 449 | tfx_glGetProgramiv = get_proc_address("glGetProgramiv"); 450 | tfx_glGetProgramInfoLog = get_proc_address("glGetProgramInfoLog"); 451 | tfx_glDeleteProgram = get_proc_address("glDeleteProgram"); 452 | tfx_glGenTextures = get_proc_address("glGenTextures"); 453 | tfx_glBindTexture = get_proc_address("glBindTexture"); 454 | tfx_glBindTextures = get_proc_address("glBindTextures"); // GL_ARB_multi_bind (GL 4.4) 455 | tfx_glTexParameteri = get_proc_address("glTexParameteri"); 456 | tfx_glTexParameteriv = get_proc_address("glTexParameteriv"); 457 | tfx_glTexParameterf = get_proc_address("glTexParameterf"); 458 | tfx_glPixelStorei = get_proc_address("glPixelStorei"); 459 | tfx_glTexImage2D = get_proc_address("glTexImage2D"); 460 | tfx_glTexImage3D = get_proc_address("glTexImage3D"); 461 | tfx_glTexImage2DMultisample = get_proc_address("glTexImage2DMultisample"); 462 | tfx_glTexStorage2D = get_proc_address("glTexStorage2D"); 463 | tfx_glTexStorage3D = get_proc_address("glTexStorage3D"); 464 | tfx_glTexStorage2DMultisample = get_proc_address("glTexStorage2DMultisample"); 465 | tfx_glTexSubImage2D = get_proc_address("glTexSubImage2D"); 466 | tfx_glTexSubImage3D = get_proc_address("glTexSubImage3D"); 467 | tfx_glInvalidateTexSubImage = get_proc_address("glInvalidateTexSubImage"); 468 | tfx_glGenerateMipmap = get_proc_address("glGenerateMipmap"); 469 | tfx_glGenFramebuffers = get_proc_address("glGenFramebuffers"); 470 | tfx_glDeleteFramebuffers = get_proc_address("glDeleteFramebuffers"); 471 | tfx_glBindFramebuffer = get_proc_address("glBindFramebuffer"); 472 | tfx_glBlitFramebuffer = get_proc_address("glBlitFramebuffer"); 473 | tfx_glCopyImageSubData = get_proc_address("glCopyImageSubData"); 474 | tfx_glFramebufferTexture2D = get_proc_address("glFramebufferTexture2D"); 475 | tfx_glInvalidateFramebuffer = get_proc_address("glInvalidateFramebuffer"); 476 | tfx_glGenRenderbuffers = get_proc_address("glGenRenderbuffers"); 477 | tfx_glBindRenderbuffer = get_proc_address("glBindRenderbuffer"); 478 | tfx_glRenderbufferStorage = get_proc_address("glRenderbufferStorage"); 479 | tfx_glRenderbufferStorageMultisample = get_proc_address("glRenderbufferStorageMultisample"); 480 | tfx_glFramebufferRenderbuffer = get_proc_address("glFramebufferRenderbuffer"); 481 | tfx_glFramebufferTexture = get_proc_address("glFramebufferTexture"); 482 | tfx_glDrawBuffers = get_proc_address("glDrawBuffers"); 483 | tfx_glReadBuffer = get_proc_address("glReadBuffer"); 484 | tfx_glCheckFramebufferStatus = get_proc_address("glCheckFramebufferStatus"); 485 | tfx_glGetUniformLocation = get_proc_address("glGetUniformLocation"); 486 | tfx_glReleaseShaderCompiler = get_proc_address("glReleaseShaderCompiler"); 487 | tfx_glGenVertexArrays = get_proc_address("glGenVertexArrays"); 488 | tfx_glBindVertexArray = get_proc_address("glBindVertexArray"); 489 | tfx_glMapBufferRange = get_proc_address("glMapBufferRange"); 490 | tfx_glBufferSubData = get_proc_address("glBufferSubData"); 491 | tfx_glUnmapBuffer = get_proc_address("glUnmapBuffer"); 492 | tfx_glUseProgram = get_proc_address("glUseProgram"); 493 | tfx_glMemoryBarrier = get_proc_address("glMemoryBarrier"); 494 | tfx_glBindBufferBase = get_proc_address("glBindBufferBase"); 495 | tfx_glDispatchCompute = get_proc_address("glDispatchCompute"); 496 | tfx_glViewport = get_proc_address("glViewport"); 497 | tfx_glViewportIndexedf = get_proc_address("glViewportIndexedf"); 498 | tfx_glScissor = get_proc_address("glScissor"); 499 | tfx_glClearColor = get_proc_address("glClearColor"); 500 | tfx_glClearDepthf = get_proc_address("glClearDepthf"); 501 | tfx_glClear = get_proc_address("glClear"); 502 | tfx_glEnable = get_proc_address("glEnable"); 503 | tfx_glDepthFunc = get_proc_address("glDepthFunc"); 504 | tfx_glDisable = get_proc_address("glDisable"); 505 | tfx_glDepthMask = get_proc_address("glDepthMask"); 506 | tfx_glFrontFace = get_proc_address("glFrontFace"); 507 | tfx_glPolygonMode = get_proc_address("glPolygonMode"); 508 | tfx_glUniform1iv = get_proc_address("glUniform1iv"); 509 | tfx_glUniform1fv = get_proc_address("glUniform1fv"); 510 | tfx_glUniform2fv = get_proc_address("glUniform2fv"); 511 | tfx_glUniform3fv = get_proc_address("glUniform3fv"); 512 | tfx_glUniform4fv = get_proc_address("glUniform4fv"); 513 | tfx_glUniformMatrix2fv = get_proc_address("glUniformMatrix2fv"); 514 | tfx_glUniformMatrix3fv = get_proc_address("glUniformMatrix3fv"); 515 | tfx_glUniformMatrix4fv = get_proc_address("glUniformMatrix4fv"); 516 | tfx_glEnableVertexAttribArray = get_proc_address("glEnableVertexAttribArray"); 517 | tfx_glVertexAttribPointer = get_proc_address("glVertexAttribPointer"); 518 | tfx_glDisableVertexAttribArray = get_proc_address("glDisableVertexAttribArray"); 519 | tfx_glActiveTexture = get_proc_address("glActiveTexture"); 520 | tfx_glDrawElementsInstanced = get_proc_address("glDrawElementsInstanced"); 521 | tfx_glDrawArraysInstanced = get_proc_address("glDrawArraysInstanced"); 522 | tfx_glDrawElements = get_proc_address("glDrawElements"); 523 | tfx_glDrawArrays = get_proc_address("glDrawArrays"); 524 | tfx_glDeleteVertexArrays = get_proc_address("glDeleteVertexArrays"); 525 | tfx_glBindFragDataLocation = get_proc_address("glBindFragDataLocation"); 526 | 527 | tfx_glPushDebugGroup = get_proc_address("glPushDebugGroup"); 528 | tfx_glPopDebugGroup = get_proc_address("glPopDebugGroup"); 529 | tfx_glInsertEventMarkerEXT = get_proc_address("glInsertEventMarkerEXT"); 530 | } 531 | 532 | static const char *tfx_sprintf(const char *fmt, ...) { 533 | va_list args; 534 | va_start(args, fmt); 535 | char *buf = NULL; 536 | int need = vsnprintf(buf, 0, fmt, args) + 1; 537 | va_end(args); 538 | va_start(args, fmt); 539 | buf = malloc(need + 1); 540 | buf[need] = '\0'; 541 | vsnprintf(buf, need, fmt, args); 542 | va_end(args); 543 | return buf; 544 | } 545 | 546 | static void tfx_printf(tfx_severity severity, const char *fmt, ...) { 547 | va_list args; 548 | va_start(args, fmt); 549 | char *buf = NULL; 550 | int need = vsnprintf(buf, 0, fmt, args) + 1; 551 | va_end(args); 552 | va_start(args, fmt); 553 | buf = malloc(need + 1); 554 | buf[need] = '\0'; 555 | vsnprintf(buf, need, fmt, args); 556 | g_platform_data.info_log(buf, severity); 557 | free(buf); 558 | va_end(args); 559 | } 560 | 561 | static void tfx_printb(tfx_severity severity, const char *k, bool v) { 562 | tfx_printf(severity, "TinyFX %s: %s", k, v ? "true" : "false"); 563 | } 564 | 565 | static float g_max_aniso = 0.0f; 566 | tfx_caps tfx_get_caps() { 567 | tfx_caps caps; 568 | memset(&caps, 0, sizeof(tfx_caps)); 569 | 570 | if (tfx_glGetStringi) { 571 | GLint ext_count = 0; 572 | CHECK(tfx_glGetIntegerv(GL_NUM_EXTENSIONS, &ext_count)); 573 | 574 | for (int i = 0; i < ext_count; i++) { 575 | const char *search = (const char*)CHECK(tfx_glGetStringi(GL_EXTENSIONS, i)); 576 | #if defined(_MSC_VER) && 0 577 | OutputDebugString(search); 578 | OutputDebugString("\n"); 579 | #endif 580 | for (int j = 0;; j++) { 581 | tfx_glext *tmp = &available_exts[j]; 582 | if (!tmp->ext) { 583 | break; 584 | } 585 | if (strcmp(tmp->ext, search) == 0) { 586 | tmp->supported = true; 587 | break; 588 | } 589 | } 590 | } 591 | } 592 | else if (tfx_glGetString) { 593 | const char *real = (const char*)CHECK(tfx_glGetString(GL_EXTENSIONS)); 594 | char *exts = tfx_strdup(real); 595 | char *pch = strtok(exts, " "); 596 | int len = 0; 597 | char **supported = NULL; 598 | while (pch != NULL) { 599 | sb_push(supported, pch); 600 | pch = strtok(NULL, " "); 601 | len++; 602 | } 603 | free(exts); 604 | 605 | int n = sb_count(supported); 606 | for (int i = 0; i < n; i++) { 607 | const char *search = supported[i]; 608 | 609 | for (int j = 0; ; j++) { 610 | tfx_glext *tmp = &available_exts[j]; 611 | if (!tmp->ext) { 612 | break; 613 | } 614 | if (strcmp(tmp->ext, search) == 0) { 615 | tmp->supported = true; 616 | break; 617 | } 618 | } 619 | } 620 | sb_free(supported); 621 | } 622 | 623 | bool gl30 = g_platform_data.context_version >= 30 && !g_platform_data.use_gles; 624 | bool gl32 = g_platform_data.context_version >= 32 && !g_platform_data.use_gles; 625 | bool gl33 = g_platform_data.context_version >= 33 && !g_platform_data.use_gles; 626 | bool gl43 = g_platform_data.context_version >= 43 && !g_platform_data.use_gles; 627 | bool gl44 = g_platform_data.context_version >= 44 && !g_platform_data.use_gles; 628 | bool gl46 = g_platform_data.context_version >= 46 && !g_platform_data.use_gles; 629 | bool gles30 = g_platform_data.context_version >= 30 && g_platform_data.use_gles; 630 | bool gles31 = g_platform_data.context_version >= 31 && g_platform_data.use_gles; 631 | 632 | caps.multisample = available_exts[0].supported || gl30; 633 | caps.compute = available_exts[1].supported || gles31 || gl43; 634 | caps.float_canvas = available_exts[2].supported || gles30 || gl30; 635 | caps.debug_marker = available_exts[3].supported || available_exts[5].supported; 636 | caps.debug_output = available_exts[4].supported || gl43; 637 | caps.memory_info = available_exts[6].supported; 638 | caps.instancing = available_exts[7].supported || gl33 || gles30; 639 | caps.seamless_cubemap = available_exts[8].supported || gl32; 640 | caps.anisotropic_filtering = available_exts[9].supported || gl46; 641 | caps.multibind = available_exts[10].supported || gl44; 642 | 643 | g_max_aniso = 0.0f; 644 | GLenum GL_TEXTURE_MAX_ANISOTROPY_EXT = 0x84FE; 645 | if (caps.anisotropic_filtering) { 646 | GLenum GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT = 0x84FF; 647 | CHECK(tfx_glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &g_max_aniso)); 648 | if (g_max_aniso <= 0.0f) { 649 | caps.anisotropic_filtering = false; 650 | } 651 | } 652 | 653 | return caps; 654 | } 655 | 656 | void tfx_dump_caps() { 657 | tfx_caps caps = tfx_get_caps(); 658 | 659 | // I am told by the docs that this can be 0. 660 | // It's not on the RPi, but since it's only a few lines of code... 661 | int release_shader_c = 0; 662 | tfx_glGetIntegerv(GL_SHADER_COMPILER, &release_shader_c); 663 | TFX_INFO("GL shader compiler control: %d", release_shader_c); 664 | TFX_INFO("GL vendor: %s", tfx_glGetString(GL_VENDOR)); 665 | TFX_INFO("GL version: %s", tfx_glGetString(GL_VERSION)); 666 | 667 | TFX_INFO("%s", "GL extensions:"); 668 | if (tfx_glGetStringi) { 669 | GLint ext_count = 0; 670 | CHECK(tfx_glGetIntegerv(GL_NUM_EXTENSIONS, &ext_count)); 671 | 672 | for (int i = 0; i < ext_count; i++) { 673 | const char *real = (const char*)CHECK(tfx_glGetStringi(GL_EXTENSIONS, i)); 674 | TFX_INFO("\t%s", real); 675 | } 676 | } 677 | else if (tfx_glGetString) { 678 | const char *real = (const char*)CHECK(tfx_glGetString(GL_EXTENSIONS)); 679 | char *exts = tfx_strdup(real); 680 | int len = 0; 681 | 682 | char *pch = strtok(exts, " "); 683 | while (pch != NULL) { 684 | TFX_INFO("\t%s", pch); 685 | pch = strtok(NULL, " "); 686 | len++; 687 | } 688 | free(exts); 689 | } 690 | 691 | #define FUG(V) "GLES" #V 692 | const char *glver = 693 | #ifdef TFX_USE_GLES // NYI 694 | FUG(TFX_USE_GLES/10) 695 | #else 696 | "GL4" 697 | #endif 698 | ; 699 | #undef FUG 700 | TFX_INFO("TinyFX renderer: %s", glver); 701 | 702 | tfx_printb(TFX_SEVERITY_INFO, "instancing", caps.instancing); 703 | tfx_printb(TFX_SEVERITY_INFO, "compute", caps.compute); 704 | tfx_printb(TFX_SEVERITY_INFO, "fp canvas", caps.float_canvas); 705 | tfx_printb(TFX_SEVERITY_INFO, "multisample", caps.multisample); 706 | } 707 | 708 | // this is all definitely not the simplest way to deal with maps for uniform 709 | // caches, but it's the simplest way I know which handles collisions. 710 | #define TFX_HASHSIZE 101 711 | 712 | typedef struct tfx_set { 713 | struct tfx_set *next; 714 | const char *key; 715 | } tfx_set; 716 | 717 | static unsigned tfx_hash(const char *s) { 718 | unsigned hashval; 719 | for (hashval = 0; *s != '\0'; s++) 720 | hashval = *s + 31 * hashval; 721 | return hashval % TFX_HASHSIZE; 722 | } 723 | 724 | static unsigned tfx_nohash(unsigned id) { 725 | return id % TFX_HASHSIZE; 726 | } 727 | 728 | static bool tfx_slookup(tfx_set **hashtab, const char *s) { 729 | #ifdef TFX_DEBUG 730 | assert(s); 731 | #endif 732 | struct tfx_set *np; 733 | for (np = hashtab[tfx_hash(s)]; np != NULL; np = np->next) { 734 | if (strcmp(s, np->key) == 0) { 735 | return true; 736 | } 737 | //TFX_WARN("collision\n"); 738 | } 739 | return false; 740 | } 741 | 742 | static void tfx_sset(tfx_set **hashtab, const char *name) { 743 | if (tfx_slookup(hashtab, name)) { 744 | return; 745 | } 746 | 747 | unsigned hashval = tfx_hash(name); 748 | tfx_set *np = malloc(sizeof(tfx_set)); 749 | np->key = name; 750 | np->next = hashtab[hashval]; 751 | hashtab[hashval] = np; 752 | } 753 | 754 | static tfx_set **tfx_set_new() { 755 | return calloc(sizeof(tfx_set*), TFX_HASHSIZE); 756 | } 757 | 758 | static void tfx_set_delete(tfx_set **hashtab) { 759 | for (int i = 0; i < TFX_HASHSIZE; i++) { 760 | if (hashtab[i] != NULL) { 761 | free(hashtab[i]); 762 | } 763 | } 764 | free(hashtab); 765 | } 766 | 767 | typedef struct tfx_locmap { 768 | struct tfx_locmap *next; 769 | const char *key; 770 | GLuint value; // uniform location 771 | } tfx_locmap; 772 | 773 | static tfx_locmap *tfx_loclookup(tfx_locmap **hashtab, const char *s) { 774 | #ifdef TFX_DEBUG 775 | assert(s); 776 | #endif 777 | struct tfx_locmap *np; 778 | for (np = hashtab[tfx_hash(s)]; np != NULL; np = np->next) { 779 | if (strcmp(s, np->key) == 0) { 780 | return np; 781 | } 782 | //TFX_WARN("collision\n"); 783 | } 784 | return NULL; 785 | } 786 | 787 | static tfx_locmap* tfx_locset(tfx_locmap **hashtab, const char *name, GLuint value) { 788 | tfx_locmap *found = tfx_loclookup(hashtab, name); 789 | if (found) { 790 | return NULL; 791 | } 792 | 793 | unsigned hashval = tfx_hash(name); 794 | tfx_locmap *np = malloc(sizeof(tfx_locmap)); 795 | np->key = name; 796 | np->next = hashtab[hashval]; 797 | np->value = value; 798 | hashtab[hashval] = np; 799 | return np; 800 | } 801 | 802 | static tfx_locmap **tfx_locmap_new() { 803 | return calloc(sizeof(tfx_locmap*), TFX_HASHSIZE); 804 | } 805 | 806 | static void tfx_locmap_delete(tfx_locmap **hashtab) { 807 | for (int i = 0; i < TFX_HASHSIZE; i++) { 808 | if (hashtab[i] != NULL) { 809 | free(hashtab[i]); 810 | } 811 | } 812 | free(hashtab); 813 | } 814 | 815 | typedef struct tfx_shadermap { 816 | struct tfx_shadermap *next; 817 | GLint key; // shader program 818 | tfx_locmap **value; 819 | } tfx_shadermap; 820 | 821 | static tfx_shadermap *tfx_proglookup(tfx_shadermap **hashtab, GLint program) { 822 | #ifdef TFX_DEBUG 823 | assert(program); 824 | #endif 825 | struct tfx_shadermap *np; 826 | for (np = hashtab[tfx_nohash(program)]; np != NULL; np = np->next) { 827 | if (program == np->key) { 828 | return np; 829 | } 830 | //TFX_WARN("collision\n"); 831 | } 832 | return NULL; 833 | } 834 | 835 | static tfx_shadermap* tfx_progset(tfx_shadermap **hashtab, GLint program) { 836 | tfx_shadermap *found = tfx_proglookup(hashtab, program); 837 | if (found) { 838 | return found; 839 | } 840 | 841 | unsigned hashval = tfx_nohash(program); 842 | tfx_shadermap *np = malloc(sizeof(tfx_shadermap)); 843 | np->key = program; 844 | np->next = hashtab[hashval]; 845 | np->value = tfx_locmap_new(); 846 | hashtab[hashval] = np; 847 | return np; 848 | } 849 | 850 | static tfx_shadermap **tfx_progmap_new() { 851 | return calloc(sizeof(tfx_shadermap*), TFX_HASHSIZE); 852 | } 853 | 854 | static void tfx_progmap_delete(tfx_shadermap **hashtab) { 855 | for (int i = 0; i < TFX_HASHSIZE; i++) { 856 | if (hashtab[i] != NULL) { 857 | tfx_locmap_delete(hashtab[i]->value); 858 | free(hashtab[i]); 859 | } 860 | } 861 | free(hashtab); 862 | } 863 | 864 | static tfx_buffer *g_buffers; 865 | 866 | typedef struct tfx_frame_state { 867 | // uniforms updated this frame 868 | tfx_uniform *uniforms; 869 | uint8_t *uniform_buffer; 870 | uint8_t *ub_cursor; 871 | tfx_shadermap **uniform_map; 872 | tfx_view views[VIEW_MAX]; 873 | } tfx_frame_state; 874 | 875 | static tfx_frame_state g_back; // staging update 876 | /* 877 | // TODO 878 | static tfx_frame_state g_front; // processing update 879 | 880 | void swap_state_buffer() { 881 | tfx_frame_state tmp; 882 | memcpy(&tmp, &g_back, sizeof(tfx_frame_state)); 883 | 884 | g_back = g_front; 885 | g_front = tmp; 886 | } 887 | */ 888 | 889 | static struct { 890 | uint8_t *data; 891 | uint32_t offset; 892 | tfx_buffer buffers[TFX_TRANSIENT_BUFFER_COUNT]; 893 | } g_transient_buffer; 894 | 895 | static tfx_caps g_caps; 896 | 897 | // fallback printf 898 | static void basic_log(const char *msg, tfx_severity level) { 899 | #ifdef _MSC_VER 900 | OutputDebugString(msg); 901 | OutputDebugString("\n"); 902 | printf("%s\n", msg); 903 | #else 904 | printf("%s\n", msg); 905 | #endif 906 | } 907 | 908 | static void tvb_reset() { 909 | g_transient_buffer.offset = 0; 910 | 911 | for (int i = 0; i < TFX_TRANSIENT_BUFFER_COUNT; i++) { 912 | if (!g_transient_buffer.buffers[i].gl_id) { 913 | GLuint id; 914 | CHECK(tfx_glGenBuffers(1, &id)); 915 | CHECK(tfx_glBindBuffer(GL_ARRAY_BUFFER, id)); 916 | if (tfx_glBufferStorage) { 917 | CHECK(tfx_glBufferStorage(GL_ARRAY_BUFFER, TFX_TRANSIENT_BUFFER_SIZE, NULL, GL_DYNAMIC_STORAGE_BIT | GL_MAP_WRITE_BIT)); 918 | } 919 | else { 920 | CHECK(tfx_glBufferData(GL_ARRAY_BUFFER, TFX_TRANSIENT_BUFFER_SIZE, NULL, GL_DYNAMIC_DRAW)); 921 | } 922 | g_transient_buffer.buffers[i].gl_id = id; 923 | } 924 | } 925 | } 926 | 927 | void tfx_set_platform_data(tfx_platform_data pd) { 928 | // supported: GL > 3, 2.1, ES 2.0, ES 3.0+ 929 | assert(0 930 | || (pd.context_version >= 30) 931 | || (pd.use_gles && pd.context_version == 20) 932 | || (!pd.use_gles && pd.context_version == 21) 933 | ); 934 | if (pd.info_log == NULL) { 935 | pd.info_log = &basic_log; 936 | } 937 | memcpy(&g_platform_data, &pd, sizeof(tfx_platform_data)); 938 | } 939 | 940 | // null format = index buffer 941 | tfx_transient_buffer tfx_transient_buffer_new(tfx_vertex_format *fmt, uint16_t num_verts) { 942 | // transient index buffers aren't supported yet 943 | assert(fmt != NULL); 944 | assert(fmt->stride > 0); 945 | 946 | tfx_transient_buffer buf; 947 | memset(&buf, 0, sizeof(tfx_transient_buffer)); 948 | buf.data = g_transient_buffer.data + g_transient_buffer.offset; 949 | buf.num = num_verts; 950 | buf.offset = g_transient_buffer.offset; 951 | uint32_t stride = sizeof(uint16_t); 952 | if (fmt) { 953 | buf.has_format = true; 954 | buf.format = *fmt; 955 | stride = (uint32_t)fmt->stride; 956 | } 957 | g_transient_buffer.offset += (uint32_t)(num_verts * stride); 958 | g_transient_buffer.offset += g_transient_buffer.offset % 4; // align, in case the stride is weird 959 | return buf; 960 | } 961 | 962 | // null format = available indices (uint16) 963 | uint32_t tfx_transient_buffer_get_available(tfx_vertex_format *fmt) { 964 | assert(fmt->stride > 0); 965 | uint32_t avail = TFX_TRANSIENT_BUFFER_SIZE; 966 | avail -= g_transient_buffer.offset; 967 | uint32_t stride = sizeof(uint16_t); 968 | if (fmt) { 969 | stride = fmt->stride; 970 | } 971 | avail /= (uint32_t)stride; 972 | return avail; 973 | } 974 | 975 | static tfx_program *g_programs = NULL; 976 | static tfx_texture *g_textures = NULL; 977 | static tfx_reset_flags g_flags = TFX_RESET_NONE; 978 | static GLuint g_timers[TIMER_COUNT]; 979 | static int g_timer_offset = 0; 980 | static bool use_timers = false; 981 | 982 | static uint32_t *g_debug_data = NULL; 983 | static tfx_program g_debug_program = 0; 984 | static tfx_texture g_debug_overlay; 985 | static tfx_uniform g_debug_texture; 986 | 987 | static const char *g_debug_fss_legacy = 988 | "in vec2 f_coord;\n" 989 | "uniform sampler2D _tfx_texture;\n" 990 | "void main() {\n" 991 | " vec4 color = texture2D(_tfx_texture, vec2(f_coord.x, 1.0 - f_coord.y));\n" 992 | " if (color.a < 0.01) discard;\n" 993 | " gl_FragColor = vec4(color.rgb, 1.0) * color.a;\n" 994 | "}\n" 995 | ; 996 | 997 | static const char *g_debug_vss = 998 | "in vec3 v_position;\n" 999 | "out vec2 f_coord;\n" 1000 | "void main() {\n" 1001 | " f_coord = v_position.xy * 0.5 + 0.5;\n" 1002 | " gl_Position = vec4(v_position.xyz, 1.0);\n" 1003 | "}\n" 1004 | ; 1005 | 1006 | static const char *g_debug_fss = 1007 | "in vec2 f_coord;\n" 1008 | "out vec4 out_color;\n" 1009 | "uniform sampler2D _tfx_texture;\n" 1010 | "void main() {\n" 1011 | " vec4 color = texture(_tfx_texture, vec2(f_coord.x, 1.0 - f_coord.y));\n" 1012 | " if (color.a < 0.01) discard;\n" 1013 | " out_color = vec4(color.rgb, 1.0) * color.a;\n" 1014 | "}\n" 1015 | ; 1016 | 1017 | static const char *g_debug_attribs[] = { "v_position", NULL }; 1018 | static bool did_you_call_tfx_reset = false; 1019 | 1020 | void tfx_reset(uint16_t width, uint16_t height, tfx_reset_flags flags) { 1021 | if (g_platform_data.gl_get_proc_address != NULL) { 1022 | load_em_up(g_platform_data.gl_get_proc_address); 1023 | } 1024 | 1025 | did_you_call_tfx_reset = true; 1026 | 1027 | g_caps = tfx_get_caps(); 1028 | // we require these, unless/until backporting for pre-compute HW. 1029 | assert(g_caps.instancing); 1030 | assert(g_caps.compute); 1031 | 1032 | g_flags = TFX_RESET_NONE; 1033 | if (g_caps.anisotropic_filtering && (flags & TFX_RESET_MAX_ANISOTROPY) == TFX_RESET_MAX_ANISOTROPY) { 1034 | g_flags |= TFX_RESET_MAX_ANISOTROPY; 1035 | } 1036 | 1037 | use_timers = false; 1038 | if (tfx_glQueryCounter && (flags & TFX_RESET_REPORT_GPU_TIMINGS) == TFX_RESET_REPORT_GPU_TIMINGS) { 1039 | g_flags |= TFX_RESET_REPORT_GPU_TIMINGS; 1040 | use_timers = true; 1041 | } 1042 | 1043 | if (g_debug_data != NULL) { 1044 | free(g_debug_data); 1045 | g_debug_data = NULL; 1046 | } 1047 | if (g_debug_overlay.gl_count > 0) { 1048 | tfx_texture_free(&g_debug_overlay); 1049 | } 1050 | 1051 | if ((flags & TFX_RESET_DEBUG_OVERLAY) == TFX_RESET_DEBUG_OVERLAY) { 1052 | if (g_debug_program == 0) { 1053 | if (g_platform_data.context_version < 30) { 1054 | g_debug_program = tfx_program_new(g_debug_vss, g_debug_fss_legacy, g_debug_attribs, -1); 1055 | } 1056 | else { 1057 | g_debug_program = tfx_program_new(g_debug_vss, g_debug_fss, g_debug_attribs, -1); 1058 | } 1059 | assert(g_debug_program); 1060 | } 1061 | if (g_debug_texture.name == NULL) { 1062 | g_debug_texture = tfx_uniform_new("_tfx_texture", TFX_UNIFORM_INT, 1); 1063 | } 1064 | g_flags |= TFX_RESET_DEBUG_OVERLAY; 1065 | 1066 | if ((flags & TFX_RESET_DEBUG_OVERLAY_STATS) == TFX_RESET_DEBUG_OVERLAY_STATS) { 1067 | g_flags |= TFX_RESET_DEBUG_OVERLAY_STATS; 1068 | } 1069 | 1070 | // align texture size up to nearest character size 1071 | uint16_t ow = (uint16_t)width; 1072 | ow += ow % 8; 1073 | uint16_t oh = (uint16_t)height; 1074 | oh += oh % 8; 1075 | size_t mem = (size_t)ow * oh * 4; 1076 | g_debug_data = malloc(mem); 1077 | g_debug_overlay = tfx_texture_new(ow, oh, 1, NULL, TFX_FORMAT_RGBA8, TFX_TEXTURE_CPU_WRITABLE | TFX_TEXTURE_FILTER_POINT); 1078 | } 1079 | 1080 | memset(&g_backbuffer, 0, sizeof(tfx_canvas)); 1081 | //g_backbuffer.gl_fbo = g_platform_data.backbuffer_fbo; 1082 | g_backbuffer.allocated = 1; 1083 | g_backbuffer.width = width; 1084 | g_backbuffer.height = height; 1085 | g_backbuffer.current_width = width; 1086 | g_backbuffer.current_height = height; 1087 | g_backbuffer.attachments[0].width = width; 1088 | g_backbuffer.attachments[0].height = height; 1089 | g_backbuffer.attachments[0].depth = 1; 1090 | 1091 | if (!g_back.uniform_buffer) { 1092 | g_back.uniform_buffer = (uint8_t*)malloc(TFX_UNIFORM_BUFFER_SIZE); 1093 | memset(g_back.uniform_buffer, 0, TFX_UNIFORM_BUFFER_SIZE); 1094 | g_back.ub_cursor = g_back.uniform_buffer; 1095 | } 1096 | 1097 | if (!g_transient_buffer.data) { 1098 | g_transient_buffer.data = (uint8_t*)malloc(TFX_TRANSIENT_BUFFER_SIZE); 1099 | memset(g_transient_buffer.data, 0xfc, TFX_TRANSIENT_BUFFER_SIZE); 1100 | tvb_reset(); 1101 | } 1102 | 1103 | if (!g_back.uniform_map) { 1104 | g_back.uniform_map = tfx_progmap_new(); 1105 | } 1106 | 1107 | // update every already loaded texture's anisotropy to max (typically 16) or 0 1108 | if (g_caps.anisotropic_filtering) { 1109 | int nt = sb_count(g_textures); 1110 | if (g_max_aniso > 0.0f) { 1111 | for (int i = 0; i < nt; i++) { 1112 | tfx_texture *tex = &g_textures[i]; 1113 | for (unsigned j = 0; j < tex->gl_count; j++) { 1114 | if ((tex->flags & TFX_TEXTURE_MSAA_SAMPLE) == TFX_TEXTURE_MSAA_SAMPLE) { 1115 | continue; 1116 | } 1117 | GLenum fmt = GL_TEXTURE_2D; 1118 | if ((tex->flags & TFX_TEXTURE_CUBE) == TFX_TEXTURE_CUBE) { 1119 | fmt = GL_TEXTURE_CUBE_MAP; 1120 | } 1121 | const GLenum GL_TEXTURE_MAX_ANISOTROPY_EXT = 0x84FE; 1122 | CHECK(tfx_glBindTexture(fmt, tex->gl_ids[j])); 1123 | CHECK(tfx_glTexParameterf(fmt, GL_TEXTURE_MAX_ANISOTROPY_EXT, g_max_aniso)); 1124 | } 1125 | } 1126 | } 1127 | } 1128 | 1129 | #if defined(_MSC_VER) && defined(TFX_DEBUG) 1130 | if (g_caps.memory_info) { 1131 | GLint memory; 1132 | // GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX 1133 | CHECK(tfx_glGetIntegerv(0x9048, &memory)); 1134 | char buf[64]; 1135 | snprintf(buf, 64, "VRAM: %dMiB\n", memory / 1024); 1136 | OutputDebugString(buf); 1137 | } 1138 | #endif 1139 | 1140 | memset(&g_back.views, 0, sizeof(tfx_view)*VIEW_MAX); 1141 | 1142 | // not supported in ES2 w/o exts 1143 | if (tfx_glQueryCounter) { 1144 | if (g_timers[0] != 0) { 1145 | CHECK(tfx_glDeleteQueries(TIMER_COUNT, g_timers)); 1146 | } 1147 | } 1148 | if (use_timers) { 1149 | CHECK(tfx_glGenQueries(TIMER_COUNT, g_timers)); 1150 | // dummy queries for first update 1151 | for (unsigned i = 0; i < TIMER_COUNT; i++) { 1152 | CHECK(tfx_glQueryCounter(g_timers[i], GL_TIMESTAMP)); 1153 | } 1154 | g_timer_offset = 0; 1155 | } 1156 | } 1157 | 1158 | void tfx_shutdown() { 1159 | tfx_frame(); 1160 | 1161 | if (tfx_glQueryCounter && g_timers[0] != 0) { 1162 | CHECK(tfx_glDeleteQueries(TIMER_COUNT, g_timers)); 1163 | } 1164 | 1165 | // TODO: clean up all GL objects, allocs, etc. 1166 | free(g_back.uniform_buffer); 1167 | g_back.uniform_buffer = NULL; 1168 | 1169 | free(g_transient_buffer.data); 1170 | g_transient_buffer.data = NULL; 1171 | 1172 | for (int i = 0; i < TFX_TRANSIENT_BUFFER_COUNT; i++) { 1173 | if (g_transient_buffer.buffers[i].gl_id) { 1174 | tfx_glDeleteBuffers(1, &g_transient_buffer.buffers[i].gl_id); 1175 | } 1176 | } 1177 | 1178 | if (g_back.uniform_map) { 1179 | tfx_progmap_delete(g_back.uniform_map); 1180 | g_back.uniform_map = NULL; 1181 | } 1182 | 1183 | // this can happen if you shutdown before calling frame() 1184 | if (g_back.uniforms) { 1185 | sb_free(g_back.uniforms); 1186 | g_back.uniforms = NULL; 1187 | } 1188 | 1189 | int nt = sb_count(g_textures); 1190 | while (nt-- > 0) { 1191 | tfx_texture_free(&g_textures[nt]); 1192 | } 1193 | sb_free(g_textures); 1194 | 1195 | int nb = sb_count(g_buffers); 1196 | while (nb-- > 0) { 1197 | tfx_buffer_free(&g_buffers[nb]); 1198 | } 1199 | sb_free(g_buffers); 1200 | 1201 | tfx_glUseProgram(0); 1202 | int np = sb_count(g_programs); 1203 | for (int i = 0; i < np; i++) { 1204 | tfx_glDeleteProgram(g_programs[i]); 1205 | } 1206 | sb_free(g_programs); 1207 | 1208 | #ifdef TFX_LEAK_CHECK 1209 | stb_leakcheck_dumpmem(); 1210 | #endif 1211 | } 1212 | 1213 | static bool g_shaderc_allocated = false; 1214 | static int stack_depth = 0; 1215 | 1216 | static void push_group(unsigned id, const char *label) { 1217 | if (tfx_glPushDebugGroup) { 1218 | CHECK(tfx_glPushDebugGroup(GL_DEBUG_SOURCE_APPLICATION, id, strlen(label), label)); 1219 | stack_depth += 1; 1220 | } 1221 | } 1222 | 1223 | static void pop_group() { 1224 | if (tfx_glPopDebugGroup && stack_depth > 0) { 1225 | CHECK(tfx_glPopDebugGroup()); 1226 | stack_depth -= 1; 1227 | } 1228 | } 1229 | 1230 | const char *legacy_vs_prepend = "" 1231 | "#ifndef GL_ES\n" 1232 | "#define lowp\n" 1233 | "#define mediump\n" 1234 | "#define highp\n" 1235 | "#else\n" 1236 | "precision highp float;\n" 1237 | "#define in attribute\n" 1238 | "#define out varying\n" 1239 | "#endif\n" 1240 | "#pragma optionNV(strict on)\n" 1241 | "#define main _pain\n" 1242 | "#define tfx_viewport_count 1\n" 1243 | "#define VERTEX 1\n" 1244 | "#line 1\n" 1245 | ; 1246 | const char *legacy_fs_prepend = "" 1247 | "#ifndef GL_ES\n" 1248 | "#define lowp\n" 1249 | "#define mediump\n" 1250 | "#define highp\n" 1251 | "#else\n" 1252 | "precision mediump float;\n" 1253 | "#define in varying\n" 1254 | "#endif\n" 1255 | "#pragma optionNV(strict on)\n" 1256 | "#define PIXEL 1\n" 1257 | "#line 1\n" 1258 | ; 1259 | const char *gs_prepend = "" 1260 | "#ifdef GL_ES\n" 1261 | "precision highp float;\n" 1262 | "#endif\n" 1263 | "#define GEOMETRY 1\n" 1264 | "#line 1\n" 1265 | ; 1266 | const char *vs_prepend = "" 1267 | "#ifdef GL_ES\n" 1268 | "precision highp float;\n" 1269 | "#endif\n" 1270 | "#define main _pain\n" 1271 | "#define tfx_viewport_count 1\n" 1272 | "#define VERTEX 1\n" 1273 | "#line 1\n" 1274 | ; 1275 | const char *fs_prepend = "" 1276 | "#ifdef GL_ES\n" 1277 | "precision mediump float;\n" 1278 | "#endif\n" 1279 | "#define PIXEL 1\n" 1280 | "#line 1\n" 1281 | ; 1282 | 1283 | const char *vs_append = "" 1284 | "#undef main\n" 1285 | "void main() {\n" 1286 | " _pain();\n" 1287 | // TODO: enable extensions if GL < 4.1, add gl_Layer support for 1288 | // single pass cube/shadow cascade rendering 1289 | "#if 0\n" 1290 | " gl_ViewportIndex = gl_InstanceID % tfx_viewport_count;\n" 1291 | "#endif\n" 1292 | "}\n" 1293 | ; 1294 | 1295 | const char *cs_prepend = "" 1296 | "#define COMPUTE 1\n" 1297 | "#line 1\n" 1298 | ; 1299 | 1300 | static char *sappend(const char *left, const char *right, const int right_len) { 1301 | size_t ls = strlen(left); 1302 | size_t rs = (size_t)right_len; 1303 | char *ss = (char*)malloc(ls+rs+1); 1304 | memcpy(ss, left, ls); 1305 | memcpy(ss+ls, right, rs); 1306 | ss[ls+rs] = '\0'; 1307 | return ss; 1308 | } 1309 | 1310 | static char *shader_concat(const char *base, GLenum shader_type, const int base_len) { 1311 | bool legacy = g_platform_data.context_version < 30; 1312 | 1313 | const char *prepend = ""; 1314 | const char *append = ""; 1315 | 1316 | switch (shader_type) { 1317 | case GL_COMPUTE_SHADER: { 1318 | assert(g_caps.compute); 1319 | prepend = cs_prepend; 1320 | break; 1321 | } 1322 | case GL_VERTEX_SHADER: { 1323 | prepend = legacy ? legacy_vs_prepend : vs_prepend; 1324 | append = vs_append; 1325 | break; 1326 | } 1327 | case GL_FRAGMENT_SHADER: { 1328 | prepend = legacy ? legacy_fs_prepend: fs_prepend; 1329 | break; 1330 | } 1331 | case GL_GEOMETRY_SHADER: { 1332 | prepend = gs_prepend; 1333 | break; 1334 | } 1335 | default: break; 1336 | } 1337 | 1338 | char *ss1 = sappend(prepend, base, base_len); 1339 | 1340 | char version[64]; 1341 | int gl_major = g_platform_data.context_version / 10; 1342 | int gl_minor = g_platform_data.context_version % 10; 1343 | const char *suffix = " core"; 1344 | // post GL3/GLES3, versions are sane, just fix suffix. 1345 | if (g_platform_data.use_gles && g_platform_data.context_version >= 30) { 1346 | suffix = " es"; 1347 | } 1348 | // GL and GLES2 use GLSL 1xx versions 1349 | else if (g_platform_data.context_version < 30) { 1350 | suffix = ""; 1351 | gl_major = 1; 1352 | // GL 2.1 -> GLSL 120 1353 | if (!g_platform_data.use_gles) { 1354 | gl_minor = 2; 1355 | } 1356 | } 1357 | snprintf(version, 64, "#version %d%d0%s\n", gl_major, gl_minor, suffix); 1358 | 1359 | char *ss2 = sappend(ss1, append, strlen(append)); 1360 | free(ss1); 1361 | 1362 | char *ss = sappend(version, ss2, strlen(ss2)); 1363 | free(ss2); 1364 | 1365 | return ss; 1366 | } 1367 | 1368 | static GLuint load_shader(GLenum type, const char *shaderSrc, const int len) { 1369 | g_shaderc_allocated = true; 1370 | 1371 | GLuint shader = CHECK(tfx_glCreateShader(type)); 1372 | if (!shader) { 1373 | TFX_FATAL("%s", "Something has gone horribly wrong, and we can't make shaders."); 1374 | return 0; 1375 | } 1376 | 1377 | if (len <= 0) { 1378 | return 0; 1379 | } 1380 | 1381 | char *ss = shader_concat(shaderSrc, type, len); 1382 | CHECK(tfx_glShaderSource(shader, 1, (const char**)&ss, NULL)); 1383 | CHECK(tfx_glCompileShader(shader)); 1384 | 1385 | GLint compiled; 1386 | CHECK(tfx_glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled)); 1387 | if (!compiled) { 1388 | GLint infoLen = 0; 1389 | CHECK(tfx_glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen)); 1390 | if (infoLen > 0) { 1391 | char* infoLog = (char*)malloc(sizeof(char) * infoLen); 1392 | CHECK(tfx_glGetShaderInfoLog(shader, infoLen, NULL, infoLog)); 1393 | TFX_ERROR("Error compiling shader:\n%s", infoLog); 1394 | // TFX_ERROR("FULL SHADER\n\n\n%s\n\n\n", ss); 1395 | #ifdef _MSC_VER 1396 | OutputDebugString(infoLog); 1397 | #endif 1398 | free(infoLog); 1399 | } 1400 | CHECK(tfx_glDeleteShader(shader)); 1401 | #ifdef TFX_DEBUG 1402 | assert(compiled); 1403 | #endif 1404 | free(ss); 1405 | return 0; 1406 | } 1407 | // free this a bit late to make debugging easier 1408 | free(ss); 1409 | return shader; 1410 | } 1411 | 1412 | static bool try_program_link(GLuint program) { 1413 | CHECK(tfx_glLinkProgram(program)); 1414 | GLint linked; 1415 | CHECK(tfx_glGetProgramiv(program, GL_LINK_STATUS, &linked)); 1416 | if (!linked) { 1417 | GLint infoLen = 0; 1418 | CHECK(tfx_glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen)); 1419 | if (infoLen > 0) { 1420 | char* infoLog = (char*)malloc(infoLen); 1421 | CHECK(tfx_glGetProgramInfoLog(program, infoLen, NULL, infoLog)); 1422 | TFX_ERROR("Error linking program:\n%s", infoLog); 1423 | free(infoLog); 1424 | } 1425 | return false; 1426 | } 1427 | return true; 1428 | } 1429 | 1430 | tfx_program tfx_program_gs_len_new(const char *_gss, const int _gs_len, const char *_vss, const int _vs_len, const char *_fss, const int _fs_len, const char *attribs[], const int attrib_count) { 1431 | assert(did_you_call_tfx_reset); 1432 | 1433 | GLuint gs = 0; 1434 | if (_gss) { 1435 | gs = load_shader(GL_GEOMETRY_SHADER, _gss, _gs_len); 1436 | } 1437 | GLuint vs = 0; 1438 | if (_vss) { 1439 | vs = load_shader(GL_VERTEX_SHADER, _vss, _vs_len); 1440 | } 1441 | GLuint fs = 0; 1442 | if (_fss) { 1443 | fs = load_shader(GL_FRAGMENT_SHADER, _fss, _fs_len); 1444 | } 1445 | GLuint program = CHECK(tfx_glCreateProgram()); 1446 | if (!program) { 1447 | return 0; 1448 | } 1449 | 1450 | if (gs) { 1451 | CHECK(tfx_glAttachShader(program, gs)); 1452 | } 1453 | if (vs) { 1454 | CHECK(tfx_glAttachShader(program, vs)); 1455 | } 1456 | if (fs) { 1457 | CHECK(tfx_glAttachShader(program, fs)); 1458 | } 1459 | 1460 | if (attrib_count >= 0) { 1461 | for (int i = 0; i < attrib_count; i++) { 1462 | CHECK(tfx_glBindAttribLocation(program, i, attribs[i])); 1463 | } 1464 | } 1465 | else { 1466 | const char **it = attribs; 1467 | int i = 0; 1468 | while (*it != NULL) { 1469 | CHECK(tfx_glBindAttribLocation(program, i, *it)); 1470 | i++; 1471 | it++; 1472 | } 1473 | } 1474 | 1475 | // TODO: accept frag data binding array 1476 | if (tfx_glBindFragDataLocation) { 1477 | //CHECK(tfx_glBindFragDataLocation(program, 0, "out_color")); 1478 | } 1479 | 1480 | if (!try_program_link(program)) { 1481 | CHECK(tfx_glDeleteProgram(program)); 1482 | return 0; 1483 | } 1484 | 1485 | if (gs) { 1486 | CHECK(tfx_glDeleteShader(gs)); 1487 | } 1488 | CHECK(tfx_glDeleteShader(vs)); 1489 | CHECK(tfx_glDeleteShader(fs)); 1490 | 1491 | sb_push(g_programs, program); 1492 | 1493 | return program; 1494 | } 1495 | 1496 | tfx_program tfx_program_gs_new(const char *_gss, const char *_vss, const char *_fss, const char *attribs[], const int attrib_count) { 1497 | int gs_len = 0; 1498 | int vs_len = 0; 1499 | int fs_len = 0; 1500 | if (_gss) { 1501 | gs_len = strlen(_gss); 1502 | } 1503 | if (_vss) { 1504 | vs_len = strlen(_vss); 1505 | } 1506 | if (_fss) { 1507 | fs_len = strlen(_fss); 1508 | } 1509 | return tfx_program_gs_len_new(_gss, gs_len, _vss, vs_len, _fss, fs_len, attribs, attrib_count); 1510 | } 1511 | 1512 | tfx_program tfx_program_cs_len_new(const char *css, const int cs_len) { 1513 | assert(did_you_call_tfx_reset); 1514 | 1515 | if (!g_caps.compute) { 1516 | return 0; 1517 | } 1518 | 1519 | GLuint cs = load_shader(GL_COMPUTE_SHADER, css, cs_len); 1520 | GLuint program = CHECK(tfx_glCreateProgram()); 1521 | if (!program) { 1522 | return 0; 1523 | } 1524 | CHECK(tfx_glAttachShader(program, cs)); 1525 | if (!try_program_link(program)) { 1526 | CHECK(tfx_glDeleteProgram(program)); 1527 | return 0; 1528 | } 1529 | CHECK(tfx_glDeleteShader(cs)); 1530 | 1531 | sb_push(g_programs, program); 1532 | 1533 | return program; 1534 | } 1535 | 1536 | tfx_program tfx_program_cs_new(const char *_css) { 1537 | int cs_len = 0; 1538 | if (_css) { 1539 | cs_len = strlen(_css); 1540 | } 1541 | return tfx_program_cs_len_new(_css, cs_len); 1542 | } 1543 | 1544 | tfx_program tfx_program_new(const char *_vss, const char *_fss, const char *attribs[], const int attrib_count) { 1545 | return tfx_program_gs_new(NULL, _vss, _fss, attribs, attrib_count); 1546 | } 1547 | 1548 | tfx_program tfx_program_len_new(const char *_vss, const int _vs_len, const char *_fss, const int _fs_len, const char *attribs[], const int attrib_count) { 1549 | return tfx_program_gs_len_new(NULL, 0, _vss, _vs_len, _fss, _fs_len, attribs, attrib_count); 1550 | } 1551 | 1552 | tfx_vertex_format tfx_vertex_format_start() { 1553 | tfx_vertex_format fmt; 1554 | memset(&fmt, 0, sizeof(tfx_vertex_format)); 1555 | 1556 | return fmt; 1557 | } 1558 | 1559 | void tfx_vertex_format_add(tfx_vertex_format *fmt, uint8_t slot, size_t count, bool normalized, tfx_component_type type) { 1560 | assert(type >= 0 && type <= TFX_TYPE_SKIP); 1561 | 1562 | if (slot >= fmt->count) { 1563 | fmt->count = slot + 1; 1564 | } 1565 | tfx_vertex_component *component = &fmt->components[slot]; 1566 | memset(component, 0, sizeof(tfx_vertex_component)); 1567 | component->offset = 0; 1568 | component->size = count; 1569 | component->normalized = normalized; 1570 | component->type = type; 1571 | 1572 | fmt->component_mask |= 1 << slot; 1573 | } 1574 | 1575 | size_t tfx_vertex_format_offset(tfx_vertex_format *fmt, uint8_t slot) { 1576 | assert(slot < 8); 1577 | return fmt->components[slot].offset; 1578 | } 1579 | 1580 | void tfx_vertex_format_end(tfx_vertex_format *fmt) { 1581 | size_t stride = 0; 1582 | int nc = fmt->count; 1583 | for (int i = 0; i < nc; i++) { 1584 | tfx_vertex_component *vc = &fmt->components[i]; 1585 | size_t bytes = 0; 1586 | switch (vc->type) { 1587 | case TFX_TYPE_SKIP: 1588 | case TFX_TYPE_UBYTE: 1589 | case TFX_TYPE_BYTE: bytes = 1; break; 1590 | case TFX_TYPE_USHORT: 1591 | case TFX_TYPE_SHORT: bytes = 2; break; 1592 | case TFX_TYPE_FLOAT: bytes = 4; break; 1593 | default: assert(0); break; 1594 | } 1595 | vc->offset = stride; 1596 | stride += vc->size * bytes; 1597 | } 1598 | fmt->stride = stride; 1599 | } 1600 | 1601 | tfx_buffer tfx_buffer_new(const void *data, size_t size, tfx_vertex_format *format, tfx_buffer_flags flags) { 1602 | assert(did_you_call_tfx_reset); 1603 | 1604 | GLenum gl_usage = GL_STATIC_DRAW; 1605 | switch (flags) { 1606 | case TFX_BUFFER_MUTABLE: gl_usage = GL_DYNAMIC_DRAW; break; 1607 | //case TFX_BUFFER_STREAM: gl_usage = GL_STREAM_DRAW; break; 1608 | default: break; 1609 | //default: assert(0); break; 1610 | } 1611 | 1612 | tfx_buffer buffer; 1613 | memset(&buffer, 0, sizeof(tfx_buffer)); 1614 | buffer.gl_id = 0; 1615 | buffer.flags = flags; 1616 | if (format) { 1617 | assert(format->stride > 0); 1618 | 1619 | buffer.has_format = true; 1620 | buffer.format = *format; 1621 | } 1622 | 1623 | CHECK(tfx_glGenBuffers(1, &buffer.gl_id)); 1624 | CHECK(tfx_glBindBuffer(GL_ARRAY_BUFFER, buffer.gl_id)); 1625 | 1626 | if (size != 0) { 1627 | if (tfx_glBufferStorage) { 1628 | CHECK(tfx_glBufferStorage(GL_ARRAY_BUFFER, size, data, (gl_usage == GL_DYNAMIC_DRAW ? (GL_DYNAMIC_STORAGE_BIT | GL_MAP_WRITE_BIT) : 0))) 1629 | } 1630 | else { 1631 | CHECK(tfx_glBufferData(GL_ARRAY_BUFFER, size, data, gl_usage)); 1632 | } 1633 | } 1634 | 1635 | sb_push(g_buffers, buffer); 1636 | 1637 | return buffer; 1638 | } 1639 | 1640 | typedef struct tfx_buffer_params { 1641 | uint32_t offset; 1642 | uint32_t size; 1643 | const void *update_data; 1644 | } tfx_buffer_params; 1645 | 1646 | static tfx_buffer *get_internal_buffer(tfx_buffer *buf) { 1647 | int nb = sb_count(g_buffers); 1648 | for (int i = 0; i < nb; i++) { 1649 | if (g_buffers[i].gl_id == buf->gl_id) { 1650 | return &g_buffers[i]; 1651 | } 1652 | } 1653 | return NULL; 1654 | } 1655 | 1656 | void tfx_buffer_update(tfx_buffer *buf, const void *data, uint32_t offset, uint32_t size) { 1657 | assert(buf != NULL); 1658 | assert((buf->flags & TFX_BUFFER_MUTABLE) == TFX_BUFFER_MUTABLE); 1659 | assert(size > 0); 1660 | assert(data != NULL); 1661 | tfx_buffer_params *params = buf->internal; 1662 | if (!buf->internal) { 1663 | params = calloc(1, sizeof(tfx_buffer_params)); 1664 | } 1665 | params->update_data = data; 1666 | params->offset = offset; 1667 | params->size = size; 1668 | get_internal_buffer(buf)->internal = params; 1669 | } 1670 | 1671 | void tfx_buffer_free(tfx_buffer *buf) { 1672 | CHECK(tfx_glDeleteBuffers(1, &buf->gl_id)); 1673 | if (buf->internal) { 1674 | free(buf->internal); 1675 | buf->internal = NULL; 1676 | } 1677 | int nb = sb_count(g_buffers); 1678 | for (int i = 0; i < nb; i++) { 1679 | tfx_buffer *cached = &g_buffers[i]; 1680 | // we only need to check index 0, as these ids cannot overlap or be reused. 1681 | if (buf->gl_id == cached->gl_id) { 1682 | g_buffers[i] = g_buffers[nb - 1]; 1683 | // this, uh, might not be right. 1684 | stb__sbraw(g_buffers)[1] -= 1; 1685 | } 1686 | } 1687 | } 1688 | 1689 | typedef struct tfx_texture_params { 1690 | GLenum format; 1691 | GLenum internal_format; 1692 | GLenum type; 1693 | const void *update_data; 1694 | } tfx_texture_params; 1695 | 1696 | tfx_texture tfx_texture_new(uint16_t w, uint16_t h, uint16_t layers, const void *data, tfx_format format, uint16_t flags) { 1697 | assert(did_you_call_tfx_reset); 1698 | 1699 | tfx_texture t; 1700 | memset(&t, 0, sizeof(tfx_texture)); 1701 | 1702 | t.width = w; 1703 | t.height = h; 1704 | t.depth = layers; 1705 | t.format = format; 1706 | t.flags = flags; 1707 | 1708 | t.gl_count = 1; 1709 | 1710 | bool msaa_sample = (flags & TFX_TEXTURE_MSAA_SAMPLE) == TFX_TEXTURE_MSAA_SAMPLE; 1711 | 1712 | // double buffer the texture updates, to reduce stalling. 1713 | if ((flags & TFX_TEXTURE_CPU_WRITABLE) == TFX_TEXTURE_CPU_WRITABLE || msaa_sample) { 1714 | t.gl_count = 2; 1715 | } 1716 | 1717 | int samples = 1; 1718 | if ((flags & TFX_TEXTURE_MSAA_X2) == TFX_TEXTURE_MSAA_X2) { 1719 | assert(t.gl_count == 1 || msaa_sample); 1720 | samples = 2; 1721 | } 1722 | if ((flags & TFX_TEXTURE_MSAA_X4) == TFX_TEXTURE_MSAA_X4) { 1723 | assert(t.gl_count == 1 || msaa_sample); 1724 | samples = 4; 1725 | } 1726 | 1727 | t.gl_idx = 0; 1728 | 1729 | tfx_texture_params *params = calloc(1, sizeof(tfx_texture_params)); 1730 | params->update_data = NULL; 1731 | 1732 | // TODO: add some stencil formats (i.e. D24S8) 1733 | bool stencil = false; 1734 | bool depth = false; 1735 | switch (format) { 1736 | // integer formats 1737 | case TFX_FORMAT_RGB565: 1738 | params->format = GL_RGB; 1739 | params->internal_format = GL_RGB565; 1740 | params->type = GL_UNSIGNED_SHORT_5_6_5; 1741 | break; 1742 | case TFX_FORMAT_SRGB8: 1743 | params->format = GL_RGB; 1744 | params->internal_format = GL_SRGB8; 1745 | params->type = GL_UNSIGNED_BYTE; 1746 | break; 1747 | case TFX_FORMAT_SRGB8_A8: 1748 | params->format = GL_RGBA; 1749 | params->internal_format = GL_SRGB8_ALPHA8; 1750 | params->type = GL_UNSIGNED_BYTE; 1751 | break; 1752 | case TFX_FORMAT_RGBA8: 1753 | params->format = GL_RGBA; 1754 | params->internal_format = GL_RGBA8; 1755 | params->type = GL_UNSIGNED_BYTE; 1756 | break; 1757 | case TFX_FORMAT_RGB10A2: 1758 | params->format = GL_RGBA; 1759 | params->internal_format = GL_RGB10_A2; 1760 | params->type = GL_UNSIGNED_INT_10_10_10_2; 1761 | break; 1762 | case TFX_FORMAT_R32UI: 1763 | params->format = GL_RED; 1764 | params->internal_format = GL_R32UI; 1765 | params->type = GL_UNSIGNED_INT; 1766 | break; 1767 | // float formats 1768 | case TFX_FORMAT_RG11B10F: 1769 | params->format = GL_RGB; 1770 | params->internal_format = GL_R11F_G11F_B10F; 1771 | params->type = GL_FLOAT; 1772 | break; 1773 | case TFX_FORMAT_RGB16F: 1774 | params->format = GL_RGB; 1775 | params->internal_format = GL_RGB16F; 1776 | params->type = GL_FLOAT; 1777 | break; 1778 | case TFX_FORMAT_RGBA16F: 1779 | params->format = GL_RGBA; 1780 | params->internal_format = GL_RGBA16F; 1781 | params->type = GL_FLOAT; 1782 | break; 1783 | case TFX_FORMAT_R16F: 1784 | params->format = GL_RED; 1785 | params->internal_format = GL_R16F; 1786 | params->type = GL_FLOAT; 1787 | break; 1788 | case TFX_FORMAT_R32F: 1789 | params->format = GL_RED; 1790 | params->internal_format = GL_R32F; 1791 | params->type = GL_FLOAT; 1792 | break; 1793 | case TFX_FORMAT_RG16F: 1794 | params->format = GL_RG; 1795 | params->internal_format = GL_RG16F; 1796 | params->type = GL_FLOAT; 1797 | break; 1798 | case TFX_FORMAT_RG32F: 1799 | params->format = GL_RG; 1800 | params->internal_format = GL_RG32F; 1801 | params->type = GL_FLOAT; 1802 | break; 1803 | // depth formats 1804 | case TFX_FORMAT_D16: 1805 | params->format = GL_DEPTH_COMPONENT; 1806 | params->internal_format = GL_DEPTH_COMPONENT16; 1807 | params->type = GL_UNSIGNED_BYTE; 1808 | depth = true; 1809 | break; 1810 | case TFX_FORMAT_D24: 1811 | params->format = GL_DEPTH_COMPONENT; 1812 | params->internal_format = GL_DEPTH_COMPONENT24; 1813 | params->type = GL_UNSIGNED_BYTE; 1814 | depth = true; 1815 | break; 1816 | case TFX_FORMAT_D32: 1817 | params->format = GL_DEPTH_COMPONENT; 1818 | params->internal_format = GL_DEPTH_COMPONENT32; 1819 | params->type = GL_UNSIGNED_INT; 1820 | depth = true; 1821 | break; 1822 | case TFX_FORMAT_D32F: 1823 | params->format = GL_DEPTH_COMPONENT; 1824 | params->internal_format = GL_DEPTH_COMPONENT32F; 1825 | params->type = GL_FLOAT; 1826 | depth = true; 1827 | break; 1828 | // invalid 1829 | case TFX_FORMAT_RGB565_D16: 1830 | case TFX_FORMAT_RGBA8_D16: 1831 | case TFX_FORMAT_RGBA8_D24: 1832 | default: 1833 | assert(0); 1834 | break; 1835 | } 1836 | t.is_stencil = stencil; 1837 | t.is_depth = depth; 1838 | t.internal = params; 1839 | 1840 | if (samples > 1 && t.gl_count == 1) { 1841 | CHECK(tfx_glGenRenderbuffers(1, &t.gl_msaa_id)); 1842 | CHECK(tfx_glBindRenderbuffer(GL_RENDERBUFFER, t.gl_msaa_id)); 1843 | CHECK(tfx_glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, params->internal_format, w, h)); 1844 | } 1845 | 1846 | CHECK(tfx_glGenTextures(t.gl_count, t.gl_ids)); 1847 | bool aniso = (g_flags & TFX_RESET_MAX_ANISOTROPY) == TFX_RESET_MAX_ANISOTROPY; 1848 | bool reserve_mips = (flags & TFX_TEXTURE_RESERVE_MIPS) == TFX_TEXTURE_RESERVE_MIPS; 1849 | bool gen_mips = (flags & TFX_TEXTURE_GEN_MIPS) == TFX_TEXTURE_GEN_MIPS; 1850 | bool cube = (flags & TFX_TEXTURE_CUBE) == TFX_TEXTURE_CUBE; 1851 | GLenum mode = 0; 1852 | if (layers > 1) { 1853 | if (cube) { 1854 | assert(0); // only supported by GL4.0+ or with ARB_texture_cube_map_array 1855 | mode = GL_TEXTURE_CUBE_MAP_ARRAY; 1856 | } 1857 | else { 1858 | mode = GL_TEXTURE_2D_ARRAY; 1859 | } 1860 | } 1861 | else { 1862 | if (cube) { 1863 | mode = GL_TEXTURE_CUBE_MAP; 1864 | } 1865 | else { 1866 | mode = GL_TEXTURE_2D; 1867 | } 1868 | } 1869 | 1870 | bool mip_filter = reserve_mips || gen_mips; 1871 | if (mip_filter) { 1872 | assert(!msaa_sample); 1873 | t.mip_count = 1 + (int)floorf(log2f(fmaxf(t.width, t.height))); 1874 | } 1875 | 1876 | for (unsigned i = 0; i < t.gl_count; i++) { 1877 | assert(t.gl_ids[i] > 0); 1878 | 1879 | bool msaa = msaa_sample && i == 1; 1880 | if (msaa) { 1881 | assert(layers == 1); 1882 | assert(!cube); 1883 | assert(!reserve_mips); 1884 | mode = GL_TEXTURE_2D_MULTISAMPLE; 1885 | CHECK(tfx_glBindTexture(mode, t.gl_ids[i])); 1886 | if (tfx_glTexStorage2DMultisample) { 1887 | CHECK(tfx_glTexStorage2DMultisample(mode, samples, params->internal_format, w, h, false)); 1888 | } 1889 | else { 1890 | CHECK(tfx_glTexImage2DMultisample(mode, samples, params->internal_format, w, h, false)); 1891 | } 1892 | continue; 1893 | } 1894 | 1895 | CHECK(tfx_glBindTexture(mode, t.gl_ids[i])); 1896 | if ((flags & TFX_TEXTURE_FILTER_POINT) == TFX_TEXTURE_FILTER_POINT) { 1897 | CHECK(tfx_glTexParameteri(mode, GL_TEXTURE_MIN_FILTER, mip_filter ? GL_NEAREST_MIPMAP_NEAREST : GL_NEAREST)); 1898 | CHECK(tfx_glTexParameteri(mode, GL_TEXTURE_MAG_FILTER, GL_NEAREST)); 1899 | } 1900 | else { // default filter: linear 1901 | CHECK(tfx_glTexParameteri(mode, GL_TEXTURE_MIN_FILTER, mip_filter ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR)); 1902 | CHECK(tfx_glTexParameteri(mode, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); 1903 | } 1904 | 1905 | if (cube) { 1906 | CHECK(tfx_glTexParameteri(mode, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE)); 1907 | } 1908 | CHECK(tfx_glTexParameteri(mode, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); 1909 | CHECK(tfx_glTexParameteri(mode, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); 1910 | 1911 | if (aniso) { 1912 | GLenum GL_TEXTURE_MAX_ANISOTROPY_EXT = 0x84FE; 1913 | CHECK(tfx_glTexParameterf(mode, GL_TEXTURE_MAX_ANISOTROPY_EXT, g_max_aniso)); 1914 | } 1915 | 1916 | // if mips are reserved, this isn't a shadow map but instead something like hi-z buffer. can't ref compare. 1917 | if (depth && !reserve_mips) { 1918 | CHECK(tfx_glTexParameteri(mode, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE)); 1919 | 1920 | // note: GL 3.3, ES 3.0+. combined swizzle isn't in gles. 1921 | GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; 1922 | CHECK(tfx_glTexParameteri(mode, GL_TEXTURE_SWIZZLE_R, swizzleMask[0])); 1923 | CHECK(tfx_glTexParameteri(mode, GL_TEXTURE_SWIZZLE_G, swizzleMask[1])); 1924 | CHECK(tfx_glTexParameteri(mode, GL_TEXTURE_SWIZZLE_B, swizzleMask[2])); 1925 | CHECK(tfx_glTexParameteri(mode, GL_TEXTURE_SWIZZLE_A, swizzleMask[3])); 1926 | } 1927 | CHECK(tfx_glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); 1928 | 1929 | if (layers > 1) { 1930 | if (cube) { 1931 | assert(0); 1932 | } 1933 | else if (tfx_glTexStorage3D) { 1934 | // mipmaps are currently unsupported for 3d/array textures. 1935 | CHECK(tfx_glTexStorage3D(mode, 1, params->internal_format, w, h, layers)); 1936 | if (data) { 1937 | CHECK(tfx_glTexSubImage3D(mode, 0, 0, 0, 0, w, h, layers, params->format, params->type, data)); 1938 | } 1939 | } 1940 | else { 1941 | CHECK(tfx_glTexImage3D(mode, 0, params->internal_format, w, h, layers, 0, params->format, params->type, data)); 1942 | } 1943 | } 1944 | if (cube) { 1945 | const uint16_t size = w > h ? w : h; 1946 | if (tfx_glTexStorage2D) { 1947 | CHECK(tfx_glTexStorage2D(mode, mip_filter ? t.mip_count : 1, params->internal_format, size, size)); 1948 | } 1949 | else { 1950 | uint16_t mip_size = size; 1951 | for (i = 0; i < t.mip_count; i++) { 1952 | for (int j = 0; j < 6; j++) { 1953 | CHECK(tfx_glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + j, 0, params->internal_format, size, size, 0, params->format, params->type, NULL)); 1954 | } 1955 | mip_size /= 2; 1956 | mip_size = mip_size > 0 ? mip_size : 1; 1957 | } 1958 | } 1959 | } 1960 | else if (tfx_glTexStorage2D) { 1961 | CHECK(tfx_glTexStorage2D(mode, mip_filter ? t.mip_count : 1, params->internal_format, w, h)); 1962 | if (data) { 1963 | CHECK(tfx_glTexSubImage2D(mode, 0, 0, 0, w, h, params->format, params->type, data)); 1964 | } 1965 | } 1966 | else { 1967 | CHECK(tfx_glTexImage2D(mode, 0, params->internal_format, w, h, 0, params->format, params->type, data)); 1968 | if (reserve_mips) { 1969 | uint16_t current_width = t.width; 1970 | uint16_t current_height = t.height; 1971 | 1972 | for (unsigned i = 1; i < t.mip_count; i++) { 1973 | // calculate next viewport size 1974 | current_width /= 2; 1975 | current_height /= 2; 1976 | 1977 | // ensure that the viewport size is always at least 1x1 1978 | current_width = current_width > 0 ? current_width : 1; 1979 | current_height = current_height > 0 ? current_height : 1; 1980 | 1981 | CHECK(tfx_glTexImage2D(mode, i, params->internal_format, current_width, current_height, 0, params->format, params->type, NULL)); 1982 | } 1983 | } 1984 | } 1985 | if (gen_mips) { 1986 | CHECK(tfx_glGenerateMipmap(mode)); 1987 | } 1988 | } 1989 | 1990 | sb_push(g_textures, t); 1991 | 1992 | return t; 1993 | } 1994 | 1995 | void tfx_texture_update(tfx_texture *tex, const void *data) { 1996 | assert((tex->flags & TFX_TEXTURE_CPU_WRITABLE) == TFX_TEXTURE_CPU_WRITABLE); 1997 | tfx_texture_params *internal = tex->internal; 1998 | internal->update_data = data; 1999 | } 2000 | 2001 | void tfx_texture_free(tfx_texture *tex) { 2002 | int nt = sb_count(g_textures); 2003 | for (int i = 0; i < nt; i++) { 2004 | tfx_texture *cached = &g_textures[i]; 2005 | // we only need to check index 0, as these ids cannot overlap or be reused. 2006 | if (tex->gl_ids[0] == cached->gl_ids[0]) { 2007 | tfx_texture_params *internal = (tfx_texture_params*)cached->internal; 2008 | free(internal); 2009 | tfx_glDeleteTextures(cached->gl_count, cached->gl_ids); 2010 | g_textures[i] = g_textures[nt-1]; 2011 | // this, uh, might not be right. 2012 | stb__sbraw(g_textures)[1] -= 1; 2013 | } 2014 | } 2015 | } 2016 | 2017 | bool canvas_reconfigure(tfx_canvas *c, bool msaa) { 2018 | bool found_color = false; 2019 | bool found_depth = false; 2020 | 2021 | int offset = 0; 2022 | for (unsigned i = 0; i < c->allocated; i++) { 2023 | GLenum attach = GL_COLOR_ATTACHMENT0 + offset; 2024 | // TODO: depth stencil 2025 | if (c->attachments[i].is_depth) { 2026 | assert(!found_depth); // two depth buffers, bail 2027 | attach = GL_DEPTH_ATTACHMENT; 2028 | found_depth = true; 2029 | } 2030 | else { 2031 | found_color = true; 2032 | offset += 1; 2033 | } 2034 | 2035 | if (c->cube || c->attachments[i].depth > 1) { 2036 | CHECK(tfx_glFramebufferTexture(GL_FRAMEBUFFER, attach, c->attachments[i].gl_ids[0], 0)); 2037 | continue; 2038 | } 2039 | 2040 | GLenum mode = GL_TEXTURE_2D; 2041 | GLuint id = c->attachments[i].gl_ids[0]; 2042 | if (msaa && (c->attachments[i].flags & TFX_TEXTURE_MSAA_SAMPLE) == TFX_TEXTURE_MSAA_SAMPLE) { 2043 | mode = GL_TEXTURE_2D_MULTISAMPLE; 2044 | id = c->attachments[i].gl_ids[1]; 2045 | } 2046 | CHECK(tfx_glFramebufferTexture2D(GL_FRAMEBUFFER, attach, mode, id, 0)); 2047 | } 2048 | 2049 | if (found_depth && !found_color) { 2050 | GLenum none = GL_NONE; 2051 | CHECK(tfx_glDrawBuffers(1, &none)); 2052 | CHECK(tfx_glReadBuffer(GL_NONE)); 2053 | } 2054 | 2055 | if (found_color) { 2056 | GLenum buffers[8] = { 2057 | GL_COLOR_ATTACHMENT0 + 0, 2058 | GL_COLOR_ATTACHMENT0 + 1, 2059 | GL_COLOR_ATTACHMENT0 + 2, 2060 | GL_COLOR_ATTACHMENT0 + 3, 2061 | GL_COLOR_ATTACHMENT0 + 4, 2062 | GL_COLOR_ATTACHMENT0 + 5, 2063 | GL_COLOR_ATTACHMENT0 + 6, 2064 | GL_COLOR_ATTACHMENT0 + 7 2065 | }; 2066 | CHECK(tfx_glDrawBuffers(offset, buffers)); 2067 | CHECK(tfx_glReadBuffer(GL_COLOR_ATTACHMENT0)); 2068 | } 2069 | 2070 | // TODO: return something more error-y 2071 | GLenum status = CHECK(tfx_glCheckFramebufferStatus(GL_FRAMEBUFFER)); 2072 | if (status != GL_FRAMEBUFFER_COMPLETE) { 2073 | assert(0); 2074 | return false; 2075 | } 2076 | 2077 | return true; 2078 | } 2079 | 2080 | tfx_canvas tfx_canvas_attachments_new(bool claim_attachments, int count, tfx_texture *attachments) { 2081 | assert(did_you_call_tfx_reset); 2082 | 2083 | tfx_canvas c; 2084 | memset(&c, 0, sizeof(tfx_canvas)); 2085 | 2086 | c.allocated = count; 2087 | c.width = attachments[0].width; 2088 | c.height = attachments[0].height; 2089 | c.current_width = c.width; 2090 | c.current_height = c.height; 2091 | c.own_attachments = claim_attachments; 2092 | c.cube = (attachments[0].flags & TFX_TEXTURE_CUBE) == TFX_TEXTURE_CUBE; 2093 | bool msaa = (attachments[0].flags & TFX_TEXTURE_MSAA_X2) == TFX_TEXTURE_MSAA_X2; 2094 | if ((attachments[0].flags & TFX_TEXTURE_MSAA_X4) == TFX_TEXTURE_MSAA_X4) { 2095 | msaa = true; 2096 | } 2097 | c.msaa = msaa; 2098 | assert(!(c.msaa && c.cube)); 2099 | assert(!(c.msaa && attachments[0].depth > 1)); 2100 | 2101 | bool msaa_sample = (attachments[0].flags & TFX_TEXTURE_MSAA_SAMPLE) == TFX_TEXTURE_MSAA_SAMPLE; 2102 | for (int i = 0; i < count; i++) { 2103 | assert(attachments[i].gl_count == 1 || (msaa && msaa_sample)); 2104 | assert(attachments[i].depth == attachments[0].depth); 2105 | assert((attachments[i].flags & TFX_TEXTURE_CPU_WRITABLE) != TFX_TEXTURE_CPU_WRITABLE); 2106 | c.attachments[i] = attachments[i]; 2107 | } 2108 | 2109 | // and now the fbo. 2110 | CHECK(tfx_glGenFramebuffers(c.msaa ? 2 : 1, c.gl_fbo)); 2111 | CHECK(tfx_glBindFramebuffer(GL_FRAMEBUFFER, c.gl_fbo[0])); 2112 | 2113 | if (!canvas_reconfigure(&c, false)) { 2114 | tfx_canvas_free(&c); 2115 | return c; 2116 | } 2117 | 2118 | if (c.gl_fbo[1]) { 2119 | CHECK(tfx_glBindFramebuffer(GL_FRAMEBUFFER, c.gl_fbo[1])); 2120 | 2121 | if (msaa_sample) { 2122 | if (!canvas_reconfigure(&c, true)) { 2123 | tfx_canvas_free(&c); 2124 | } 2125 | return c; 2126 | } 2127 | 2128 | int offset = 0; 2129 | // sanity checking was already done by canvas_reconfigure, no need here. 2130 | for (unsigned i = 0; i < c.allocated; i++) { 2131 | GLenum attach = GL_COLOR_ATTACHMENT0 + offset; 2132 | if (c.attachments[i].is_depth && c.attachments[i].is_stencil) { 2133 | attach = GL_DEPTH_STENCIL_ATTACHMENT; 2134 | } 2135 | else if (c.attachments[i].is_stencil) { 2136 | attach = GL_STENCIL_ATTACHMENT; 2137 | } 2138 | else if (c.attachments[i].is_depth) { 2139 | attach = GL_DEPTH_ATTACHMENT; 2140 | } 2141 | else { 2142 | offset += 1; 2143 | } 2144 | CHECK(tfx_glFramebufferRenderbuffer(GL_FRAMEBUFFER, attach, GL_RENDERBUFFER, c.attachments[i].gl_msaa_id)); 2145 | } 2146 | } 2147 | 2148 | return c; 2149 | } 2150 | 2151 | void tfx_canvas_free(tfx_canvas *c) { 2152 | if (!c->allocated) { 2153 | return; 2154 | } 2155 | CHECK(tfx_glDeleteFramebuffers(c->msaa ? 2 : 1, c->gl_fbo)); 2156 | if (!c->own_attachments) { 2157 | return; 2158 | } 2159 | for (unsigned i = 0; i < c->allocated; i++) { 2160 | tfx_texture *attach = &c->attachments[i]; 2161 | tfx_texture_free(attach); 2162 | } 2163 | c->allocated = 0; 2164 | c->gl_fbo[0] = 0; 2165 | c->gl_fbo[1] = 0; 2166 | } 2167 | 2168 | tfx_canvas tfx_canvas_new(uint16_t w, uint16_t h, tfx_format format, uint16_t flags) { 2169 | assert(did_you_call_tfx_reset); 2170 | 2171 | tfx_texture attachments[2]; 2172 | int n = 0; 2173 | 2174 | if ((flags & TFX_TEXTURE_EXTERNAL) == TFX_TEXTURE_EXTERNAL) { 2175 | tfx_canvas c; 2176 | memset(&c, 0, sizeof(tfx_canvas)); 2177 | c.allocated = 0; 2178 | c.own_attachments = false; 2179 | c.width = w; 2180 | c.height = h; 2181 | c.current_height = h; 2182 | c.current_width = w; 2183 | return c; 2184 | } 2185 | 2186 | bool has_color = false; 2187 | bool has_depth = false; 2188 | 2189 | tfx_format color_fmt = TFX_FORMAT_RGBA8; 2190 | tfx_format depth_fmt = TFX_FORMAT_D16; 2191 | 2192 | switch (format) { 2193 | // just data 2194 | case TFX_FORMAT_R16F: 2195 | case TFX_FORMAT_R32F: 2196 | case TFX_FORMAT_RG16F: 2197 | case TFX_FORMAT_RG32F: 2198 | // color formats 2199 | case TFX_FORMAT_RGB565: 2200 | case TFX_FORMAT_RGBA8: 2201 | case TFX_FORMAT_RGB10A2: 2202 | case TFX_FORMAT_RG11B10F: 2203 | case TFX_FORMAT_RGBA16F: { 2204 | has_color = true; 2205 | color_fmt = format; 2206 | break; 2207 | } 2208 | // color + depth 2209 | case TFX_FORMAT_RGB565_D16: { 2210 | has_depth = true; 2211 | has_color = true; 2212 | color_fmt = TFX_FORMAT_RGB565; 2213 | depth_fmt = TFX_FORMAT_D16; 2214 | break; 2215 | } 2216 | case TFX_FORMAT_RGBA8_D16: { 2217 | has_depth = true; 2218 | has_color = true; 2219 | color_fmt = TFX_FORMAT_RGBA8; 2220 | depth_fmt = TFX_FORMAT_D16; 2221 | break; 2222 | } 2223 | case TFX_FORMAT_RGBA8_D24: { 2224 | has_depth = true; 2225 | has_color = true; 2226 | color_fmt = TFX_FORMAT_RGBA8; 2227 | depth_fmt = TFX_FORMAT_D24; 2228 | break; 2229 | } 2230 | // depth only 2231 | case TFX_FORMAT_D16: 2232 | case TFX_FORMAT_D24: { 2233 | has_depth = true; 2234 | depth_fmt = format; 2235 | break; 2236 | } 2237 | case TFX_FORMAT_D32: { 2238 | has_depth = true; 2239 | depth_fmt = format; 2240 | break; 2241 | } 2242 | default: assert(0); 2243 | } 2244 | 2245 | if (has_color) { 2246 | attachments[n++] = tfx_texture_new(w, h, 1, NULL, color_fmt, flags); 2247 | } 2248 | 2249 | if (has_depth) { 2250 | attachments[n++] = tfx_texture_new(w, h, 1, NULL, depth_fmt, flags); 2251 | } 2252 | 2253 | return tfx_canvas_attachments_new(true, n, attachments); 2254 | } 2255 | 2256 | static size_t uniform_size_for(tfx_uniform_type type) { 2257 | switch (type) { 2258 | case TFX_UNIFORM_FLOAT: return sizeof(float); 2259 | case TFX_UNIFORM_INT: return sizeof(uint32_t); 2260 | case TFX_UNIFORM_VEC2: return sizeof(float)*2; 2261 | case TFX_UNIFORM_VEC3: return sizeof(float)*3; 2262 | case TFX_UNIFORM_VEC4: return sizeof(float)*4; 2263 | case TFX_UNIFORM_MAT2: return sizeof(float)*4; 2264 | case TFX_UNIFORM_MAT3: return sizeof(float)*9; 2265 | case TFX_UNIFORM_MAT4: return sizeof(float)*16; 2266 | default: return 0; 2267 | } 2268 | return 0; 2269 | } 2270 | 2271 | tfx_uniform tfx_uniform_new(const char *name, tfx_uniform_type type, int count) { 2272 | tfx_uniform u; 2273 | memset(&u, 0, sizeof(tfx_uniform)); 2274 | 2275 | u.name = name; 2276 | u.type = type; 2277 | u.count = count; 2278 | u.last_count = count; 2279 | u.size = count * uniform_size_for(type); 2280 | 2281 | return u; 2282 | } 2283 | 2284 | void tfx_set_uniform(tfx_uniform *uniform, const float *data, const int count) { 2285 | size_t size = uniform->size; 2286 | uniform->last_count = uniform->count; 2287 | if (count >= 0) { 2288 | size = count * uniform_size_for(uniform->type); 2289 | uniform->last_count = count; 2290 | } 2291 | 2292 | uniform->data = g_back.ub_cursor; 2293 | uint32_t offset = (g_back.ub_cursor + size - g_back.uniform_buffer); 2294 | assert(offset < TFX_UNIFORM_BUFFER_SIZE); 2295 | memcpy(uniform->fdata, data, size); 2296 | g_back.ub_cursor += size; 2297 | 2298 | sb_push(g_back.uniforms, *uniform); 2299 | } 2300 | 2301 | void tfx_set_uniform_int(tfx_uniform *uniform, const int *data, const int count) { 2302 | size_t size = uniform->size; 2303 | uniform->last_count = uniform->count; 2304 | if (count >= 0) { 2305 | size = count * uniform_size_for(uniform->type); 2306 | uniform->last_count = count; 2307 | } 2308 | 2309 | uniform->data = g_back.ub_cursor; 2310 | uint32_t offset = (g_back.ub_cursor + size - g_back.uniform_buffer); 2311 | assert(offset < TFX_UNIFORM_BUFFER_SIZE); 2312 | memcpy(uniform->idata, data, size); 2313 | g_back.ub_cursor += size; 2314 | 2315 | sb_push(g_back.uniforms, *uniform); 2316 | } 2317 | 2318 | void tfx_view_set_flags(uint8_t id, tfx_view_flags flags) { 2319 | tfx_view *view = &g_back.views[id]; 2320 | #define FLAG(flags, mask) ((flags & mask) == mask) 2321 | if (FLAG(flags, TFX_VIEW_INVALIDATE)) { 2322 | view->flags |= TFXI_VIEW_INVALIDATE; 2323 | } 2324 | if (FLAG(flags, TFX_VIEW_FLUSH)) { 2325 | view->flags |= TFXI_VIEW_FLUSH; 2326 | } 2327 | // NYI 2328 | if (FLAG(flags, TFX_VIEW_SORT_SEQUENTIAL)) { 2329 | assert(0); 2330 | view->flags |= TFXI_VIEW_SORT_SEQUENTIAL; 2331 | } 2332 | #undef FLAG 2333 | } 2334 | 2335 | void tfx_view_set_transform(uint8_t id, float *_view, float *proj_l, float *proj_r) { 2336 | // TODO: reserve tfx_world_to_view, tfx_view_to_screen uniforms 2337 | tfx_view *view = &g_back.views[id]; 2338 | assert(view != NULL); 2339 | memcpy(view->view, _view, sizeof(float)*16); 2340 | memcpy(view->proj_left, proj_l, sizeof(float)*16); 2341 | memcpy(view->proj_right, proj_r, sizeof(float)*16); 2342 | } 2343 | 2344 | void tfx_view_set_name(uint8_t id, const char *name) { 2345 | tfx_view *view = &g_back.views[id]; 2346 | view->name = name; 2347 | } 2348 | 2349 | void tfx_view_set_canvas(uint8_t id, tfx_canvas *canvas, int layer) { 2350 | tfx_view *view = &g_back.views[id]; 2351 | assert(view != NULL); 2352 | view->has_canvas = true; 2353 | view->canvas = *canvas; 2354 | view->canvas_layer = layer; 2355 | } 2356 | 2357 | void tfx_view_set_clear_color(uint8_t id, unsigned color) { 2358 | tfx_view *view = &g_back.views[id]; 2359 | assert(view != NULL); 2360 | view->flags |= TFXI_VIEW_CLEAR_COLOR; 2361 | view->clear_color = color; 2362 | } 2363 | 2364 | void tfx_view_set_clear_depth(uint8_t id, float depth) { 2365 | tfx_view *view = &g_back.views[id]; 2366 | assert(view != NULL); 2367 | view->flags |= TFXI_VIEW_CLEAR_DEPTH; 2368 | view->clear_depth = depth; 2369 | } 2370 | 2371 | void tfx_view_set_depth_test(uint8_t id, tfx_depth_test mode) { 2372 | tfx_view *view = &g_back.views[id]; 2373 | assert(view != NULL); 2374 | 2375 | view->flags &= ~TFXI_VIEW_DEPTH_TEST_MASK; 2376 | switch (mode) { 2377 | case TFX_DEPTH_TEST_NONE: break; /* already cleared */ 2378 | case TFX_DEPTH_TEST_LT: { 2379 | view->flags |= TFXI_VIEW_DEPTH_TEST_LT; 2380 | break; 2381 | } 2382 | case TFX_DEPTH_TEST_GT: { 2383 | view->flags |= TFXI_VIEW_DEPTH_TEST_GT; 2384 | break; 2385 | } 2386 | case TFX_DEPTH_TEST_EQ: { 2387 | view->flags |= TFXI_VIEW_DEPTH_TEST_EQ; 2388 | break; 2389 | } 2390 | default: assert(0); break; 2391 | } 2392 | } 2393 | 2394 | static tfx_canvas *get_canvas(tfx_view *view) { 2395 | assert(view != NULL); 2396 | if (view->has_canvas) { 2397 | return &view->canvas; 2398 | } 2399 | return &g_backbuffer; 2400 | } 2401 | 2402 | tfx_canvas *tfx_view_get_canvas(uint8_t id) { 2403 | return get_canvas(&g_back.views[id]); 2404 | } 2405 | 2406 | uint16_t tfx_view_get_width(uint8_t id) { 2407 | tfx_view *view = &g_back.views[id]; 2408 | assert(view != NULL); 2409 | 2410 | if (view->has_canvas) { 2411 | return view->canvas.width; 2412 | } 2413 | 2414 | return g_backbuffer.width; 2415 | } 2416 | 2417 | uint16_t tfx_view_get_height(uint8_t id) { 2418 | tfx_view *view = &g_back.views[id]; 2419 | assert(view != NULL); 2420 | 2421 | if (view->has_canvas) { 2422 | return view->canvas.height; 2423 | } 2424 | 2425 | return g_backbuffer.height; 2426 | } 2427 | 2428 | void tfx_view_get_dimensions(uint8_t id, uint16_t *w, uint16_t *h) { 2429 | if (w) { 2430 | *w = tfx_view_get_width(id); 2431 | } 2432 | if (h) { 2433 | *h = tfx_view_get_height(id); 2434 | } 2435 | } 2436 | 2437 | void tfx_view_set_viewports(uint8_t id, int count, uint16_t **viewports) { 2438 | // as of 2019-01-04, every GPU with GL_ARB_viewport_array supports 16. 2439 | assert(count <= 16); 2440 | if (count > 1) { 2441 | assert(tfx_glViewportIndexedf); 2442 | } 2443 | 2444 | tfx_view *view = &g_back.views[id]; 2445 | view->viewport_count = count; 2446 | for (int i = 0; i < count; i++) { 2447 | view->viewports[i].x = viewports[i][0]; 2448 | view->viewports[i].y = viewports[i][1]; 2449 | view->viewports[i].w = viewports[i][2]; 2450 | view->viewports[i].h = viewports[i][3]; 2451 | } 2452 | } 2453 | 2454 | void tfx_view_set_instance_mul(uint8_t id, unsigned factor) { 2455 | if (!g_caps.instancing) { 2456 | TFX_WARN("%s", "Instancing is not supported, instance mul will be ignored!"); 2457 | } 2458 | tfx_view *view = &g_back.views[id]; 2459 | view->instance_mul = factor; 2460 | } 2461 | 2462 | void tfx_view_set_scissor(uint8_t id, uint16_t x, uint16_t y, uint16_t w, uint16_t h) { 2463 | tfx_view *view = &g_back.views[id]; 2464 | view->flags |= TFXI_VIEW_SCISSOR; 2465 | 2466 | tfx_rect rect; 2467 | rect.x = x; 2468 | rect.y = y; 2469 | rect.w = w; 2470 | rect.h = h; 2471 | 2472 | view->scissor_rect = rect; 2473 | } 2474 | 2475 | static tfx_draw g_tmp_draw; 2476 | 2477 | static void reset() { 2478 | memset(&g_tmp_draw, 0, sizeof(tfx_draw)); 2479 | } 2480 | 2481 | void tfx_set_callback(tfx_draw_callback cb) { 2482 | g_tmp_draw.callback = cb; 2483 | } 2484 | 2485 | void tfx_set_scissor(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { 2486 | g_tmp_draw.use_scissor = true; 2487 | g_tmp_draw.scissor_rect.x = x; 2488 | g_tmp_draw.scissor_rect.y = y; 2489 | g_tmp_draw.scissor_rect.w = w; 2490 | g_tmp_draw.scissor_rect.h = h; 2491 | } 2492 | 2493 | void tfx_set_texture(tfx_uniform *uniform, tfx_texture *tex, uint8_t slot) { 2494 | assert(slot <= 8); 2495 | assert(uniform != NULL); 2496 | assert(uniform->count == 1); 2497 | 2498 | uniform->data = g_back.ub_cursor; 2499 | uniform->idata[0] = slot; 2500 | g_back.ub_cursor += uniform->size; 2501 | 2502 | sb_push(g_back.uniforms, *uniform); 2503 | 2504 | assert(tex->gl_ids[tex->gl_idx] > 0); 2505 | g_tmp_draw.textures[slot] = *tex; 2506 | } 2507 | 2508 | tfx_texture tfx_get_texture(tfx_canvas *canvas, uint8_t index) { 2509 | tfx_texture tex; 2510 | memset(&tex, 0, sizeof(tfx_texture)); 2511 | 2512 | assert(index < canvas->allocated); 2513 | 2514 | memcpy(&tex, &canvas->attachments[index], sizeof(tfx_texture)); 2515 | tex.gl_count = 1; 2516 | tex.gl_ids[0] = tex.gl_ids[tex.gl_idx]; 2517 | tex.gl_idx = 0; 2518 | 2519 | // no cpu writes for textures from canvases, doesn't make sense. 2520 | tex.flags &= ~TFX_TEXTURE_CPU_WRITABLE; 2521 | 2522 | return tex; 2523 | } 2524 | 2525 | void tfx_set_state(uint64_t flags) { 2526 | g_tmp_draw.flags = flags; 2527 | } 2528 | 2529 | void tfx_set_buffer(tfx_buffer *buf, uint8_t slot, bool write) { 2530 | assert(slot < 8); 2531 | assert(buf != NULL); 2532 | g_tmp_draw.ssbos[slot] = *buf; 2533 | g_tmp_draw.ssbo_write[slot] = write; 2534 | } 2535 | 2536 | void tfx_set_image(tfx_uniform *uniform, tfx_texture *tex, uint8_t slot, uint8_t mip, bool write) { 2537 | assert(slot < 8); 2538 | assert(tex != NULL); 2539 | tfx_set_texture(uniform, tex, slot); 2540 | g_tmp_draw.textures_mip[slot] = mip; 2541 | g_tmp_draw.textures_write[slot] = write; 2542 | } 2543 | 2544 | // TODO: make this work for index buffers 2545 | void tfx_set_transient_buffer(tfx_transient_buffer tb) { 2546 | assert(tb.has_format); 2547 | g_tmp_draw.vbo = g_transient_buffer.buffers[0]; 2548 | g_tmp_draw.use_vbo = true; 2549 | g_tmp_draw.use_tvb = true; 2550 | g_tmp_draw.tvb_fmt = tb.format; 2551 | g_tmp_draw.offset = tb.offset; 2552 | g_tmp_draw.indices = tb.num; 2553 | } 2554 | 2555 | void tfx_set_vertices(tfx_buffer *vbo, int count) { 2556 | assert(vbo != NULL); 2557 | assert(vbo->has_format); 2558 | 2559 | g_tmp_draw.vbo = *vbo; 2560 | g_tmp_draw.use_vbo = true; 2561 | if (!g_tmp_draw.use_ibo) { 2562 | g_tmp_draw.indices = count; 2563 | } 2564 | } 2565 | 2566 | void tfx_set_indices(tfx_buffer *ibo, int count, int offset) { 2567 | g_tmp_draw.ibo = *ibo; 2568 | g_tmp_draw.use_ibo = true; 2569 | g_tmp_draw.offset = offset; 2570 | g_tmp_draw.indices = count; 2571 | } 2572 | 2573 | static void push_uniforms(tfx_program program, tfx_draw *add_state) { 2574 | tfx_set **found = tfx_set_new(); 2575 | 2576 | int n = sb_count(g_back.uniforms); 2577 | for (int i = n-1; i >= 0; i--) { 2578 | tfx_uniform uniform = g_back.uniforms[i]; 2579 | 2580 | tfx_shadermap *val = tfx_proglookup(g_back.uniform_map, program); 2581 | if (!val) { 2582 | val = tfx_progset(g_back.uniform_map, program); 2583 | } 2584 | #ifdef TFX_DEBUG 2585 | assert(val); 2586 | assert(val->value); 2587 | #endif 2588 | tfx_locmap **locmap = val->value; 2589 | tfx_locmap *locval = tfx_loclookup(locmap, uniform.name); 2590 | 2591 | if (!locval) { 2592 | GLint loc = CHECK(tfx_glGetUniformLocation(program, uniform.name)); 2593 | if (loc >= 0) { 2594 | locval = tfx_locset(locmap, uniform.name, loc); 2595 | } 2596 | else { 2597 | continue; 2598 | } 2599 | } 2600 | 2601 | // only record the last update for a given uniform 2602 | if (!tfx_slookup(found, uniform.name)) { 2603 | tfx_uniform found_uniform = uniform; 2604 | found_uniform.data = g_back.ub_cursor; 2605 | assert((g_back.ub_cursor + uniform.size - g_back.uniform_buffer) < TFX_UNIFORM_BUFFER_SIZE); 2606 | memcpy(found_uniform.data, uniform.data, uniform.size); 2607 | g_back.ub_cursor += uniform.size; 2608 | 2609 | tfx_sset(found, uniform.name); 2610 | sb_push(add_state->uniforms, found_uniform); 2611 | } 2612 | } 2613 | 2614 | tfx_set_delete(found); 2615 | } 2616 | 2617 | void tfx_dispatch(uint8_t id, tfx_program program, uint32_t x, uint32_t y, uint32_t z) { 2618 | tfx_view *view = &g_back.views[id]; 2619 | g_tmp_draw.program = program; 2620 | assert(program != 0); 2621 | assert(view != NULL); 2622 | assert((x*y*z) > 0); 2623 | 2624 | tfx_draw add_state; 2625 | memcpy(&add_state, &g_tmp_draw, sizeof(tfx_draw)); 2626 | add_state.threads_x = x; 2627 | add_state.threads_y = y; 2628 | add_state.threads_z = z; 2629 | 2630 | push_uniforms(program, &add_state); 2631 | sb_push(view->jobs, add_state); 2632 | 2633 | reset(); 2634 | } 2635 | 2636 | void tfx_submit(uint8_t id, tfx_program program, bool retain) { 2637 | tfx_view *view = &g_back.views[id]; 2638 | g_tmp_draw.program = program; 2639 | assert(program != 0); 2640 | assert(view != NULL); 2641 | 2642 | tfx_draw add_state; 2643 | memcpy(&add_state, &g_tmp_draw, sizeof(tfx_draw)); 2644 | push_uniforms(program, &add_state); 2645 | sb_push(view->draws, add_state); 2646 | 2647 | if (!retain) { 2648 | reset(); 2649 | } 2650 | } 2651 | 2652 | void tfx_submit_ordered(uint8_t id, tfx_program program, uint32_t depth, bool retain) { 2653 | g_tmp_draw.depth = depth; 2654 | tfx_submit(id, program, retain); 2655 | } 2656 | 2657 | void tfx_touch(uint8_t id) { 2658 | tfx_view *view = &g_back.views[id]; 2659 | assert(view != NULL); 2660 | 2661 | tfx_draw_callback cb = g_tmp_draw.callback; 2662 | uint64_t flags = g_tmp_draw.flags; 2663 | reset(); 2664 | g_tmp_draw.callback = cb; 2665 | if (g_tmp_draw.callback) { 2666 | g_tmp_draw.flags = flags; 2667 | } 2668 | sb_push(view->draws, g_tmp_draw); 2669 | g_tmp_draw.callback = NULL; 2670 | g_tmp_draw.flags = 0; 2671 | } 2672 | 2673 | void tfx_blit(uint8_t dst, uint8_t src, uint16_t x, uint16_t y, uint16_t w, uint16_t h, int mip) { 2674 | tfx_rect rect; 2675 | rect.x = x; 2676 | rect.y = y; 2677 | rect.w = w; 2678 | rect.h = h; 2679 | 2680 | tfx_blit_op blit; 2681 | blit.source = get_canvas(&g_back.views[src]); 2682 | blit.source_mip = mip; 2683 | blit.rect = rect; 2684 | blit.mask = 0; 2685 | 2686 | tfx_view *view = &g_back.views[dst]; 2687 | tfx_canvas *canvas = get_canvas(view); 2688 | 2689 | // blit to self doesn't make sense, and msaa resolve is automatic. 2690 | assert(blit.source != canvas); 2691 | 2692 | for (unsigned i = 0; i < canvas->allocated; i++) { 2693 | tfx_texture *attach = &canvas->attachments[i]; 2694 | if (attach->is_stencil) { 2695 | blit.mask |= GL_STENCIL_BUFFER_BIT; 2696 | } 2697 | if (attach->is_depth) { 2698 | blit.mask |= GL_DEPTH_BUFFER_BIT; 2699 | } 2700 | // there aren't any combined color+depth or stencil 2701 | if (!attach->is_depth && !attach->is_stencil) { 2702 | blit.mask |= GL_COLOR_BUFFER_BIT; 2703 | } 2704 | } 2705 | 2706 | sb_push(view->blits, blit); 2707 | } 2708 | 2709 | static void release_compiler() { 2710 | if (!g_shaderc_allocated) { 2711 | return; 2712 | } 2713 | 2714 | int release_shader_c = 0; 2715 | CHECK(tfx_glGetIntegerv(GL_SHADER_COMPILER, &release_shader_c)); 2716 | 2717 | if (release_shader_c) { 2718 | CHECK(tfx_glReleaseShaderCompiler()); 2719 | } 2720 | 2721 | g_shaderc_allocated = false; 2722 | } 2723 | 2724 | static void update_uniforms(tfx_draw *draw) { 2725 | int nu = sb_count(draw->uniforms); 2726 | for (int j = 0; j < nu; j++) { 2727 | tfx_uniform uniform = draw->uniforms[j]; 2728 | 2729 | tfx_shadermap *val = tfx_proglookup(g_back.uniform_map, draw->program); 2730 | tfx_locmap **locmap = val->value; 2731 | tfx_locmap *locval = tfx_loclookup(locmap, uniform.name); 2732 | #ifdef TFX_DEBUG 2733 | assert(locval); 2734 | #endif 2735 | 2736 | GLint loc = locval->value; 2737 | if (loc < 0) { 2738 | continue; 2739 | } 2740 | switch (uniform.type) { 2741 | case TFX_UNIFORM_INT: CHECK(tfx_glUniform1iv(loc, uniform.last_count, uniform.idata)); break; 2742 | case TFX_UNIFORM_FLOAT: CHECK(tfx_glUniform1fv(loc, uniform.last_count, uniform.fdata)); break; 2743 | case TFX_UNIFORM_VEC2: CHECK(tfx_glUniform2fv(loc, uniform.last_count, uniform.fdata)); break; 2744 | case TFX_UNIFORM_VEC3: CHECK(tfx_glUniform3fv(loc, uniform.last_count, uniform.fdata)); break; 2745 | case TFX_UNIFORM_VEC4: CHECK(tfx_glUniform4fv(loc, uniform.last_count, uniform.fdata)); break; 2746 | case TFX_UNIFORM_MAT2: CHECK(tfx_glUniformMatrix2fv(loc, uniform.last_count, 0, uniform.fdata)); break; 2747 | case TFX_UNIFORM_MAT3: CHECK(tfx_glUniformMatrix3fv(loc, uniform.last_count, 0, uniform.fdata)); break; 2748 | case TFX_UNIFORM_MAT4: CHECK(tfx_glUniformMatrix4fv(loc, uniform.last_count, 0, uniform.fdata)); break; 2749 | default: assert(0); break; 2750 | } 2751 | } 2752 | } 2753 | 2754 | // 8x8 font based on the ibm (?) vga bios font, based on the haxe vga text renderer example. 2755 | // TODO: change this to an 8x16 or so font, it'll look much better. 2756 | static const char vga_font[] = { 2757 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 2758 | 0x7E,0x81,0xA5,0x81,0xBD,0x99,0x81,0x7E, 2759 | 0x7E,0xFF,0x00,0xFF,0xC3,0xE7,0xFF,0x7E, 2760 | 0x6C,0xFE,0xFE,0xFE,0x7C,0x38,0x10,0x00, 2761 | 0x10,0x38,0x7C,0xFE,0x7C,0x38,0x10,0x00, 2762 | 0x38,0x7C,0x38,0xFE,0xFE,0x92,0x10,0x7C, 2763 | 0x00,0x10,0x38,0x7C,0xFE,0x7C,0x38,0x7C, 2764 | 0x00,0x00,0x18,0x3C,0x3C,0x18,0x00,0x00, 2765 | 0xFF,0xFF,0xE7,0xC3,0xC3,0xE7,0xFF,0xFF, 2766 | 0x00,0x3C,0x66,0x42,0x42,0x66,0x3C,0x00, 2767 | 0xFF,0xC3,0x99,0xBD,0xBD,0x99,0xC3,0xFF, 2768 | 0x0F,0x07,0x0F,0x7D,0xCC,0xCC,0xCC,0x78, 2769 | 0x3C,0x66,0x66,0x66,0x3C,0x18,0x7E,0x18, 2770 | 0x3F,0x33,0x3F,0x30,0x30,0x70,0xF0,0xE0, 2771 | 0x7F,0x63,0x7F,0x63,0x63,0x67,0xE6,0xC0, 2772 | 0x99,0x5A,0x3C,0xE7,0xE7,0x3C,0x5A,0x99, 2773 | 0x80,0xE0,0xF8,0xFE,0xF8,0xE0,0x80,0x00, 2774 | 0x02,0x0E,0x3E,0xFE,0x3E,0x0E,0x02,0x00, 2775 | 0x18,0x3C,0x7E,0x18,0x18,0x7E,0x3C,0x18, 2776 | 0x66,0x66,0x66,0x66,0x66,0x00,0x66,0x00, 2777 | 0x7F,0x00,0x00,0x7B,0x1B,0x1B,0x1B,0x00, 2778 | 0x3E,0x63,0x38,0x6C,0x6C,0x38,0x86,0xFC, 2779 | 0x00,0x00,0x00,0x00,0x7E,0x7E,0x7E,0x00, 2780 | 0x18,0x3C,0x7E,0x18,0x7E,0x3C,0x18,0xFF, 2781 | 0x18,0x3C,0x7E,0x18,0x18,0x18,0x18,0x00, 2782 | 0x18,0x18,0x18,0x18,0x7E,0x3C,0x18,0x00, 2783 | 0x00,0x18,0x0C,0xFE,0x0C,0x18,0x00,0x00, 2784 | 0x00,0x30,0x60,0xFE,0x60,0x30,0x00,0x00, 2785 | 0x00,0x00,0xC0,0xC0,0xC0,0xFE,0x00,0x00, 2786 | 0x00,0x24,0x66,0xFF,0x66,0x24,0x00,0x00, 2787 | 0x00,0x18,0x3C,0x7E,0xFF,0xFF,0x00,0x00, 2788 | 0x00,0xFF,0xFF,0x7E,0x3C,0x18,0x00,0x00, 2789 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 2790 | 0x18,0x3C,0x3C,0x18,0x18,0x00,0x18,0x00, 2791 | 0x6C,0x6C,0x6C,0x00,0x00,0x00,0x00,0x00, 2792 | 0x6C,0x6C,0xFE,0x6C,0xFE,0x6C,0x6C,0x00, 2793 | 0x18,0x7E,0xC0,0x7C,0x06,0xFC,0x18,0x00, 2794 | 0x00,0xC6,0xCC,0x18,0x30,0x66,0xC6,0x00, 2795 | 0x38,0x6C,0x38,0x76,0xDC,0xCC,0x76,0x00, 2796 | 0x30,0x30,0x60,0x00,0x00,0x00,0x00,0x00, 2797 | 0x18,0x30,0x60,0x60,0x60,0x30,0x18,0x00, 2798 | 0x60,0x30,0x18,0x18,0x18,0x30,0x60,0x00, 2799 | 0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00, 2800 | 0x00,0x18,0x18,0x7E,0x18,0x18,0x00,0x00, 2801 | 0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x30, 2802 | 0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00, 2803 | 0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00, 2804 | 0x06,0x0C,0x18,0x30,0x60,0xC0,0x80,0x00, 2805 | 0x7C,0xCE,0xDE,0xF6,0xE6,0xC6,0x7C,0x00, 2806 | 0x30,0x70,0x30,0x30,0x30,0x30,0xFC,0x00, 2807 | 0x78,0xCC,0x0C,0x38,0x60,0xCC,0xFC,0x00, 2808 | 0x78,0xCC,0x0C,0x38,0x0C,0xCC,0x78,0x00, 2809 | 0x1C,0x3C,0x6C,0xCC,0xFE,0x0C,0x1E,0x00, 2810 | 0xFC,0xC0,0xF8,0x0C,0x0C,0xCC,0x78,0x00, 2811 | 0x38,0x60,0xC0,0xF8,0xCC,0xCC,0x78,0x00, 2812 | 0xFC,0xCC,0x0C,0x18,0x30,0x30,0x30,0x00, 2813 | 0x78,0xCC,0xCC,0x78,0xCC,0xCC,0x78,0x00, 2814 | 0x78,0xCC,0xCC,0x7C,0x0C,0x18,0x70,0x00, 2815 | 0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x00, 2816 | 0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x30, 2817 | 0x18,0x30,0x60,0xC0,0x60,0x30,0x18,0x00, 2818 | 0x00,0x00,0x7E,0x00,0x7E,0x00,0x00,0x00, 2819 | 0x60,0x30,0x18,0x0C,0x18,0x30,0x60,0x00, 2820 | 0x3C,0x66,0x0C,0x18,0x18,0x00,0x18,0x00, 2821 | 0x7C,0xC6,0xDE,0xDE,0xDC,0xC0,0x7C,0x00, 2822 | 0x30,0x78,0xCC,0xCC,0xFC,0xCC,0xCC,0x00, 2823 | 0xFC,0x66,0x66,0x7C,0x66,0x66,0xFC,0x00, 2824 | 0x3C,0x66,0xC0,0xC0,0xC0,0x66,0x3C,0x00, 2825 | 0xF8,0x6C,0x66,0x66,0x66,0x6C,0xF8,0x00, 2826 | 0xFE,0x62,0x68,0x78,0x68,0x62,0xFE,0x00, 2827 | 0xFE,0x62,0x68,0x78,0x68,0x60,0xF0,0x00, 2828 | 0x3C,0x66,0xC0,0xC0,0xCE,0x66,0x3A,0x00, 2829 | 0xCC,0xCC,0xCC,0xFC,0xCC,0xCC,0xCC,0x00, 2830 | 0x78,0x30,0x30,0x30,0x30,0x30,0x78,0x00, 2831 | 0x1E,0x0C,0x0C,0x0C,0xCC,0xCC,0x78,0x00, 2832 | 0xE6,0x66,0x6C,0x78,0x6C,0x66,0xE6,0x00, 2833 | 0xF0,0x60,0x60,0x60,0x62,0x66,0xFE,0x00, 2834 | 0xC6,0xEE,0xFE,0xFE,0xD6,0xC6,0xC6,0x00, 2835 | 0xC6,0xE6,0xF6,0xDE,0xCE,0xC6,0xC6,0x00, 2836 | 0x38,0x6C,0xC6,0xC6,0xC6,0x6C,0x38,0x00, 2837 | 0xFC,0x66,0x66,0x7C,0x60,0x60,0xF0,0x00, 2838 | 0x7C,0xC6,0xC6,0xC6,0xD6,0x7C,0x0E,0x00, 2839 | 0xFC,0x66,0x66,0x7C,0x6C,0x66,0xE6,0x00, 2840 | 0x7C,0xC6,0xE0,0x78,0x0E,0xC6,0x7C,0x00, 2841 | 0xFC,0xB4,0x30,0x30,0x30,0x30,0x78,0x00, 2842 | 0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xFC,0x00, 2843 | 0xCC,0xCC,0xCC,0xCC,0xCC,0x78,0x30,0x00, 2844 | 0xC6,0xC6,0xC6,0xC6,0xD6,0xFE,0x6C,0x00, 2845 | 0xC6,0xC6,0x6C,0x38,0x6C,0xC6,0xC6,0x00, 2846 | 0xCC,0xCC,0xCC,0x78,0x30,0x30,0x78,0x00, 2847 | 0xFE,0xC6,0x8C,0x18,0x32,0x66,0xFE,0x00, 2848 | 0x78,0x60,0x60,0x60,0x60,0x60,0x78,0x00, 2849 | 0xC0,0x60,0x30,0x18,0x0C,0x06,0x02,0x00, 2850 | 0x78,0x18,0x18,0x18,0x18,0x18,0x78,0x00, 2851 | 0x10,0x38,0x6C,0xC6,0x00,0x00,0x00,0x00, 2852 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF, 2853 | 0x30,0x30,0x18,0x00,0x00,0x00,0x00,0x00, 2854 | 0x00,0x00,0x78,0x0C,0x7C,0xCC,0x76,0x00, 2855 | 0xE0,0x60,0x60,0x7C,0x66,0x66,0xDC,0x00, 2856 | 0x00,0x00,0x78,0xCC,0xC0,0xCC,0x78,0x00, 2857 | 0x1C,0x0C,0x0C,0x7C,0xCC,0xCC,0x76,0x00, 2858 | 0x00,0x00,0x78,0xCC,0xFC,0xC0,0x78,0x00, 2859 | 0x38,0x6C,0x64,0xF0,0x60,0x60,0xF0,0x00, 2860 | 0x00,0x00,0x76,0xCC,0xCC,0x7C,0x0C,0xF8, 2861 | 0xE0,0x60,0x6C,0x76,0x66,0x66,0xE6,0x00, 2862 | 0x30,0x00,0x70,0x30,0x30,0x30,0x78,0x00, 2863 | 0x0C,0x00,0x1C,0x0C,0x0C,0xCC,0xCC,0x78, 2864 | 0xE0,0x60,0x66,0x6C,0x78,0x6C,0xE6,0x00, 2865 | 0x70,0x30,0x30,0x30,0x30,0x30,0x78,0x00, 2866 | 0x00,0x00,0xCC,0xFE,0xFE,0xD6,0xD6,0x00, 2867 | 0x00,0x00,0xB8,0xCC,0xCC,0xCC,0xCC,0x00, 2868 | 0x00,0x00,0x78,0xCC,0xCC,0xCC,0x78,0x00, 2869 | 0x00,0x00,0xDC,0x66,0x66,0x7C,0x60,0xF0, 2870 | 0x00,0x00,0x76,0xCC,0xCC,0x7C,0x0C,0x1E, 2871 | 0x00,0x00,0xDC,0x76,0x62,0x60,0xF0,0x00, 2872 | 0x00,0x00,0x7C,0xC0,0x70,0x1C,0xF8,0x00, 2873 | 0x10,0x30,0xFC,0x30,0x30,0x34,0x18,0x00, 2874 | 0x00,0x00,0xCC,0xCC,0xCC,0xCC,0x76,0x00, 2875 | 0x00,0x00,0xCC,0xCC,0xCC,0x78,0x30,0x00, 2876 | 0x00,0x00,0xC6,0xC6,0xD6,0xFE,0x6C,0x00, 2877 | 0x00,0x00,0xC6,0x6C,0x38,0x6C,0xC6,0x00, 2878 | 0x00,0x00,0xCC,0xCC,0xCC,0x7C,0x0C,0xF8, 2879 | 0x00,0x00,0xFC,0x98,0x30,0x64,0xFC,0x00, 2880 | 0x1C,0x30,0x30,0xE0,0x30,0x30,0x1C,0x00, 2881 | 0x18,0x18,0x18,0x00,0x18,0x18,0x18,0x00, 2882 | 0xE0,0x30,0x30,0x1C,0x30,0x30,0xE0,0x00, 2883 | 0x76,0xDC,0x00,0x00,0x00,0x00,0x00,0x00, 2884 | 0x00,0x10,0x38,0x6C,0xC6,0xC6,0xFE,0x00 2885 | }; 2886 | 2887 | static void render_letter( 2888 | const int pitch, 2889 | const int charCode, 2890 | const int baseRow, 2891 | const int baseCol, 2892 | const uint32_t fg, 2893 | const uint32_t bg, 2894 | uint32_t *pixels) { 2895 | // Calculate character index in font character array 2896 | // Shifting by 3 to the left is equal to multiplying by 2^3 2897 | unsigned baseIndex = charCode << 3; 2898 | // Iterate over character raster rows 2899 | for (unsigned curRow = 0; curRow < 8; curRow++) { 2900 | // Get character raster row bits 2901 | const unsigned rowBits = vga_font[baseIndex + curRow]; 2902 | const unsigned y = baseRow + curRow + 2; 2903 | // Iterate over character raster row bits (columns) 2904 | for (unsigned curCol = 0; curCol < 8; curCol++) { 2905 | // Calculate position of pixel on image 2906 | unsigned x = baseCol + curCol + 2; 2907 | // Calculate pixel index in image data array 2908 | unsigned index = y * pitch + x; 2909 | // assert(x < g_debug_overlay.width); 2910 | // assert(y < g_debug_overlay.height); 2911 | // Determine pixel color based on current bit value 2912 | uint32_t fill = (((rowBits << curCol) & 0x80) == 0x80) ? fg : bg; 2913 | pixels[index] = fill; 2914 | } 2915 | } 2916 | } 2917 | 2918 | static tfx_transient_buffer screen_triangle() { 2919 | tfx_vertex_format fmt = tfx_vertex_format_start(); 2920 | tfx_vertex_format_add(&fmt, 0, 3, false, TFX_TYPE_FLOAT); 2921 | tfx_vertex_format_end(&fmt); 2922 | 2923 | tfx_transient_buffer tb = tfx_transient_buffer_new(&fmt, 3); 2924 | float *fdata = (float*)tb.data; 2925 | const float depth = 0.0f; 2926 | fdata[0] = -1.0f; 2927 | fdata[1] = 3.0f; 2928 | fdata[2] = depth; 2929 | fdata[3] = -1.0f; 2930 | fdata[4] = -1.0f; 2931 | fdata[5] = depth; 2932 | fdata[6] = 3.0f; 2933 | fdata[7] = -1.0f; 2934 | fdata[8] = depth; 2935 | return tb; 2936 | } 2937 | 2938 | static uint32_t g_debug_default_palette[256] = { 2939 | 0xFF000000, 0xFFAA0000, 0xFF00AA00, 0xFFAAAA00, 0xFF0000AA, 0xFFAA00AA, 0xFF0055AA, 0xFFAAAAAA, 2940 | 0xFF555555, 0xFFFF5555, 0xFF55FF55, 0xFFFFFF55, 0xFF5555FF, 0xFFFF55FF, 0xFF55FFFF, 0xFFFFFFFF, 2941 | 0xFF000000, 0xFF141414, 0xFF202020, 0xFF2C2C2C, 0xFF383838, 0xFF454545, 0xFF515151, 0xFF616161, 2942 | 0xFF717171, 0xFF828282, 0xFF929292, 0xFFA2A2A2, 0xFFB6B6B6, 0xFFCBCBCB, 0xFFE3E3E3, 0xFFFFFFFF, 2943 | 0xFFFF0000, 0xFFFF0041, 0xFFFF007D, 0xFFFF00BE, 0xFFFF00FF, 0xFFBE00FF, 0xFF7D00FF, 0xFF4100FF, 2944 | 0xFF0000FF, 0xFF0041FF, 0xFF007DFF, 0xFF00BEFF, 0xFF00FFFF, 0xFF00FFBE, 0xFF00FF7D, 0xFF00FF41, 2945 | 0xFF00FF00, 0xFF41FF00, 0xFF7DFF00, 0xFFBEFF00, 0xFFFFFF00, 0xFFFFBE00, 0xFFFF7D00, 0xFFFF4100, 2946 | 0xFFFF7D7D, 0xFFFF7D9E, 0xFFFF7DBE, 0xFFFF7DDF, 0xFFFF7DFF, 0xFFDF7DFF, 0xFFBE7DFF, 0xFF9E7DFF, 2947 | 0xFF7D7DFF, 0xFF7D9EFF, 0xFF7DBEFF, 0xFF7DDFFF, 0xFF7DFFFF, 0xFF7DFFDF, 0xFF7DFFBE, 0xFF7DFF9E, 2948 | 0xFF7DFF7D, 0xFF9EFF7D, 0xFFBEFF7D, 0xFFDFFF7D, 0xFFFFFF7D, 0xFFFFDF7D, 0xFFFFBE7D, 0xFFFF9E7D, 2949 | 0xFFFFB6B6, 0xFFFFB6C7, 0xFFFFB6DB, 0xFFFFB6EB, 0xFFFFB6FF, 0xFFEBB6FF, 0xFFDBB6FF, 0xFFC7B6FF, 2950 | 0xFFB6B6FF, 0xFFB6C7FF, 0xFFB6DBFF, 0xFFB6EBFF, 0xFFB6FFFF, 0xFFB6FFEB, 0xFFB6FFDB, 0xFFB6FFC7, 2951 | 0xFFB6FFB6, 0xFFC7FFB6, 0xFFDBFFB6, 0xFFEBFFB6, 0xFFFFFFB6, 0xFFFFEBB6, 0xFFFFDBB6, 0xFFFFC7B6, 2952 | 0xFF710000, 0xFF71001C, 0xFF710038, 0xFF710055, 0xFF710071, 0xFF550071, 0xFF380071, 0xFF1C0071, 2953 | 0xFF000071, 0xFF001C71, 0xFF003871, 0xFF005571, 0xFF007171, 0xFF007155, 0xFF007138, 0xFF00711C, 2954 | 0xFF007100, 0xFF1C7100, 0xFF387100, 0xFF557100, 0xFF717100, 0xFF715500, 0xFF713800, 0xFF711C00, 2955 | 0xFF713838, 0xFF713845, 0xFF713855, 0xFF713861, 0xFF713871, 0xFF613871, 0xFF553871, 0xFF453871, 2956 | 0xFF383871, 0xFF384571, 0xFF385571, 0xFF386171, 0xFF387171, 0xFF387161, 0xFF387155, 0xFF387145, 2957 | 0xFF387138, 0xFF457138, 0xFF557138, 0xFF617138, 0xFF717138, 0xFF716138, 0xFF715538, 0xFF714538, 2958 | 0xFF715151, 0xFF715159, 0xFF715161, 0xFF715169, 0xFF715171, 0xFF695171, 0xFF615171, 0xFF595171, 2959 | 0xFF515171, 0xFF515971, 0xFF516171, 0xFF516971, 0xFF517171, 0xFF517169, 0xFF517161, 0xFF517159, 2960 | 0xFF517151, 0xFF597151, 0xFF617151, 0xFF697151, 0xFF717151, 0xFF716951, 0xFF716151, 0xFF715951, 2961 | 0xFF410000, 0xFF410010, 0xFF410020, 0xFF410030, 0xFF410041, 0xFF300041, 0xFF200041, 0xFF100041, 2962 | 0xFF000041, 0xFF001041, 0xFF002041, 0xFF003041, 0xFF004141, 0xFF004130, 0xFF004120, 0xFF004110, 2963 | 0xFF004100, 0xFF104100, 0xFF204100, 0xFF304100, 0xFF414100, 0xFF413000, 0xFF412000, 0xFF411000, 2964 | 0xFF412020, 0xFF412028, 0xFF412030, 0xFF412038, 0xFF412041, 0xFF382041, 0xFF302041, 0xFF282041, 2965 | 0xFF202041, 0xFF202841, 0xFF203041, 0xFF203841, 0xFF204141, 0xFF204138, 0xFF204130, 0xFF204128, 2966 | 0xFF204120, 0xFF284120, 0xFF304120, 0xFF384120, 0xFF414120, 0xFF413820, 0xFF413020, 0xFF412820, 2967 | 0xFF412C2C, 0xFF412C30, 0xFF412C34, 0xFF412C3C, 0xFF412C41, 0xFF3C2C41, 0xFF342C41, 0xFF302C41, 2968 | 0xFF2C2C41, 0xFF2C3041, 0xFF2C3441, 0xFF2C3C41, 0xFF2C4141, 0xFF2C413C, 0xFF2C4134, 0xFF2C4130, 2969 | 0xFF2C412C, 0xFF30412C, 0xFF34412C, 0xFF3C412C, 0xFF41412C, 0xFF413C2C, 0xFF41342C, 0xFF41302C, 2970 | 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0xFF000000, 0x00000000 2971 | }; 2972 | 2973 | static uint32_t *g_debug_palette = g_debug_default_palette; 2974 | 2975 | void tfx_debug_set_palette(const uint32_t *palette) { 2976 | g_debug_palette = palette; 2977 | if (!g_debug_palette) { 2978 | g_debug_palette = g_debug_default_palette; 2979 | } 2980 | } 2981 | 2982 | void tfx_debug_blit_rgba(const int x, const int y, const int w, const int h, const uint32_t *pixels) { 2983 | assert(did_you_call_tfx_reset); 2984 | 2985 | if ((g_flags & TFX_RESET_DEBUG_OVERLAY) != TFX_RESET_DEBUG_OVERLAY) { 2986 | return; 2987 | } 2988 | 2989 | const unsigned bufw = g_debug_overlay.width; 2990 | const unsigned bufh = g_debug_overlay.height; 2991 | for (unsigned _y = 0; _y < (unsigned)h; _y++) { 2992 | unsigned row_base = (y + _y) * bufw; 2993 | unsigned row_local = _y * w; 2994 | for (unsigned _x = 0; _x < (unsigned)w; _x++) { 2995 | g_debug_data[row_base + _x + x] = pixels[row_local + _x]; 2996 | } 2997 | } 2998 | } 2999 | 3000 | void tfx_debug_blit_pal(const int x, const int y, const int w, const int h, const uint8_t *pixels) { 3001 | assert(did_you_call_tfx_reset); 3002 | 3003 | if ((g_flags & TFX_RESET_DEBUG_OVERLAY) != TFX_RESET_DEBUG_OVERLAY) { 3004 | return; 3005 | } 3006 | 3007 | const unsigned bufw = g_debug_overlay.width; 3008 | const unsigned bufh = g_debug_overlay.height; 3009 | for (unsigned _y = 0; _y < (unsigned)h; _y++) { 3010 | unsigned row_base = (y + _y) * bufw; 3011 | unsigned row_local = _y * w; 3012 | for (unsigned _x = 0; _x < (unsigned)w; _x++) { 3013 | uint8_t idx = pixels[row_local + _x]; 3014 | g_debug_data[row_base + _x + x] = g_debug_palette[idx]; 3015 | } 3016 | } 3017 | } 3018 | 3019 | void tfx_debug_print(const int baserow, const int basecol, const uint16_t bg_fg, const int auto_wrap, const char *str) { 3020 | assert(did_you_call_tfx_reset); 3021 | 3022 | if ((g_flags & TFX_RESET_DEBUG_OVERLAY) != TFX_RESET_DEBUG_OVERLAY) { 3023 | return; 3024 | } 3025 | 3026 | const char *head = str; 3027 | const int bufw = g_debug_overlay.width; 3028 | const int bufh = g_debug_overlay.height; 3029 | int row = baserow; 3030 | int col = basecol; 3031 | const size_t bg_idx = bg_fg >> 8; 3032 | const size_t fg_idx = bg_fg & 0xff; 3033 | const uint32_t bg = g_debug_palette[bg_idx]; 3034 | const uint32_t fg = g_debug_palette[fg_idx]; 3035 | while (*head) { 3036 | char c = *head; 3037 | if (auto_wrap) { 3038 | int skip = c == ' ' && col == 0; 3039 | if (skip) { 3040 | head++; 3041 | continue; 3042 | } 3043 | } 3044 | int newline = c == '\n' || c == '\r'; 3045 | int x = (col << 3); // + (col>>1) 3046 | int wrap = x + 8 >= bufw; 3047 | if (!auto_wrap && wrap) { 3048 | head++; 3049 | continue; 3050 | } 3051 | wrap |= newline; 3052 | if (wrap) { 3053 | col = basecol; 3054 | row += 1; 3055 | if (newline) { 3056 | head++; 3057 | } 3058 | continue; 3059 | } 3060 | int y = (row << 3); // + (row >> 1) 3061 | if (y + 8 >= bufh) { 3062 | break; 3063 | } 3064 | render_letter(bufw, *head, y, x, fg, bg, g_debug_data); 3065 | col++; 3066 | head++; 3067 | } 3068 | } 3069 | 3070 | tfx_stats tfx_frame() { 3071 | assert(did_you_call_tfx_reset); 3072 | 3073 | /* This isn't used on RPi, but should free memory on some devices. When 3074 | * you call tfx_frame, you should be done with your shader compiles for 3075 | * a good while, since that should only be done during init/loading. */ 3076 | release_compiler(); 3077 | 3078 | if (g_caps.debug_output) { 3079 | CHECK(tfx_glEnable(GL_DEBUG_OUTPUT)); 3080 | } 3081 | 3082 | tfx_stats stats; 3083 | memset(&stats, 0, sizeof(tfx_stats)); 3084 | stats.timings = last_timings; 3085 | memset(last_timings, 0, sizeof(tfx_timing_info)*VIEW_MAX); 3086 | 3087 | //CHECK(tfx_glEnable(GL_FRAMEBUFFER_SRGB)); 3088 | 3089 | // I'm not aware of any situation this is available but undesirable, 3090 | // so we use it unconditionally if possible. 3091 | if (g_caps.seamless_cubemap) { 3092 | CHECK(tfx_glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS)); 3093 | } 3094 | 3095 | GLuint vao; 3096 | if (tfx_glGenVertexArrays && tfx_glBindVertexArray) { 3097 | CHECK(tfx_glGenVertexArrays(1, &vao)); 3098 | CHECK(tfx_glBindVertexArray(vao)); 3099 | } 3100 | 3101 | unsigned debug_id = 0; 3102 | 3103 | // update the debug overlay, make sure to do this before texture update AND buffer update 3104 | if ((g_flags & TFX_RESET_DEBUG_OVERLAY) == TFX_RESET_DEBUG_OVERLAY && g_debug_data != NULL && g_debug_program) { 3105 | push_group(debug_id++, "Update Debug"); 3106 | tfx_texture_update(&g_debug_overlay, g_debug_data); 3107 | 3108 | tfx_view_set_name(254, "Debug"); 3109 | tfx_set_texture(&g_debug_texture, &g_debug_overlay, 0); 3110 | tfx_set_transient_buffer(screen_triangle()); 3111 | tfx_set_state(TFX_STATE_RGB_WRITE | TFX_STATE_ALPHA_WRITE | TFX_STATE_BLEND_ALPHA | TFX_STATE_CULL_CCW); 3112 | // one before last view id, so you can draw over it if you absolutely must in the final slot. 3113 | tfx_submit(254, g_debug_program, false); 3114 | pop_group(); 3115 | } 3116 | 3117 | push_group(debug_id++, "Update Resources"); 3118 | 3119 | if (g_transient_buffer.offset > 0) { 3120 | CHECK(tfx_glBindBuffer(GL_ARRAY_BUFFER, g_transient_buffer.buffers[0].gl_id)); 3121 | if (tfx_glMapBufferRange && tfx_glUnmapBuffer) { 3122 | // this is backed by multiple buffers, so invalidate might be pointless. need to profile. 3123 | void *ptr = tfx_glMapBufferRange(GL_ARRAY_BUFFER, 0, g_transient_buffer.offset, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT); 3124 | if (ptr) { 3125 | memcpy(ptr, g_transient_buffer.data, g_transient_buffer.offset); 3126 | CHECK(tfx_glUnmapBuffer(GL_ARRAY_BUFFER)); 3127 | } 3128 | } 3129 | else { 3130 | // orphan the buffer explicitly if mapping isn't available 3131 | CHECK(tfx_glBufferData(GL_ARRAY_BUFFER, TFX_TRANSIENT_BUFFER_SIZE, NULL, GL_DYNAMIC_DRAW)); 3132 | CHECK(tfx_glBufferSubData(GL_ARRAY_BUFFER, 0, g_transient_buffer.offset, g_transient_buffer.data)); 3133 | } 3134 | } 3135 | 3136 | int nbb = sb_count(g_buffers); 3137 | for (int i = 0; i < nbb; i++) { 3138 | tfx_buffer *buf = &g_buffers[i]; 3139 | tfx_buffer_params *params = (tfx_buffer_params*)buf->internal; 3140 | if (params) { 3141 | CHECK(tfx_glBindBuffer(GL_ARRAY_BUFFER, buf->gl_id)); 3142 | if (tfx_glMapBufferRange && tfx_glUnmapBuffer) { 3143 | void *ptr = tfx_glMapBufferRange(GL_ARRAY_BUFFER, params->offset, params->size, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT); 3144 | if (ptr) { 3145 | memcpy(ptr, params->update_data, params->size); 3146 | CHECK(tfx_glUnmapBuffer(GL_ARRAY_BUFFER)); 3147 | } 3148 | } 3149 | else { 3150 | CHECK(tfx_glBufferData(GL_ARRAY_BUFFER, params->size, NULL, GL_DYNAMIC_DRAW)); 3151 | CHECK(tfx_glBufferSubData(GL_ARRAY_BUFFER, params->offset, params->size, params->update_data)); 3152 | } 3153 | free(params); 3154 | buf->internal = NULL; 3155 | } 3156 | } 3157 | 3158 | int nt = sb_count(g_textures); 3159 | for (int i = 0; i < nt; i++) { 3160 | tfx_texture *tex = &g_textures[i]; 3161 | tfx_texture_params *internal = tex->internal; 3162 | if (internal->update_data != NULL && (tex->flags & TFX_TEXTURE_CPU_WRITABLE) == TFX_TEXTURE_CPU_WRITABLE) { 3163 | assert((tex->flags & TFX_TEXTURE_CUBE) != TFX_TEXTURE_CUBE); 3164 | // spin the buffer id before updating 3165 | tex->gl_idx = (tex->gl_idx + 1) % tex->gl_count; 3166 | tfx_glBindTexture(GL_TEXTURE_2D, tex->gl_ids[tex->gl_idx]); 3167 | if (tfx_glInvalidateTexSubImage && !g_platform_data.use_gles) { 3168 | tfx_glInvalidateTexSubImage(tex->gl_ids[tex->gl_idx], 0, 0, 0, 0, tex->width, tex->height, 1); 3169 | } 3170 | tfx_glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tex->width, tex->height, internal->format, internal->type, internal->update_data); 3171 | internal->update_data = NULL; 3172 | } 3173 | } 3174 | 3175 | // clear debug pixel data for next frame 3176 | if ((g_flags & TFX_RESET_DEBUG_OVERLAY) == TFX_RESET_DEBUG_OVERLAY && g_debug_data != NULL) { 3177 | size_t pitch = (size_t)g_debug_overlay.width * 4; 3178 | size_t overlay_size = g_debug_overlay.height * pitch; 3179 | memset(g_debug_data, 0, overlay_size); 3180 | } 3181 | 3182 | pop_group(); 3183 | 3184 | char debug_label[256]; 3185 | 3186 | tfx_canvas *last_canvas = NULL; 3187 | int last_count = 0; 3188 | GLuint last_program = 0; 3189 | GLuint64 last_result = 0; 3190 | 3191 | // flip active timers every other frame. we get results from previous frame. 3192 | uint32_t next_offset = g_timer_offset; 3193 | next_offset += VIEW_MAX + 1; 3194 | next_offset %= TIMER_COUNT; 3195 | 3196 | bool in_group = false; 3197 | 3198 | for (int id = 0; id < VIEW_MAX; id++) { 3199 | tfx_view *view = &g_back.views[id]; 3200 | 3201 | int nd = sb_count(view->draws); 3202 | int cd = sb_count(view->jobs); 3203 | if (nd == 0 && cd == 0) { 3204 | continue; 3205 | } 3206 | 3207 | if (!in_group || view->name) { 3208 | if (in_group) { 3209 | pop_group(); 3210 | } 3211 | if (view->name) { 3212 | snprintf(debug_label, 256, "%s (%d)", view->name, id); 3213 | } 3214 | else { 3215 | snprintf(debug_label, 256, "View %d", id); 3216 | } 3217 | push_group(debug_id++, debug_label); 3218 | in_group = true; 3219 | } 3220 | 3221 | if (use_timers) { 3222 | int idx = id + next_offset; 3223 | GLuint result_available = 0; 3224 | CHECK(tfx_glGetQueryObjectuiv(g_timers[idx], GL_QUERY_RESULT_AVAILABLE, &result_available)); 3225 | if (result_available) { 3226 | GLuint64 result = 0; 3227 | CHECK(tfx_glGetQueryObjectui64v(g_timers[idx], GL_QUERY_RESULT, &result)); 3228 | GLuint64 now = result - last_result; 3229 | last_result = result; 3230 | 3231 | if (stats.num_timings > 0) { 3232 | stats.timings[stats.num_timings-1].time += now; 3233 | } 3234 | 3235 | if (view->name) { 3236 | stats.timings[stats.num_timings].id = id; 3237 | stats.timings[stats.num_timings].name = view->name; 3238 | stats.num_timings += 1; 3239 | } 3240 | } 3241 | CHECK(tfx_glQueryCounter(g_timers[id + g_timer_offset], GL_TIMESTAMP)); 3242 | } 3243 | 3244 | stats.draws += nd; 3245 | 3246 | tfx_canvas *canvas = get_canvas(view); 3247 | 3248 | bool canvas_changed = last_canvas && canvas != last_canvas && last_canvas->gl_fbo[0] != canvas->gl_fbo[0]; 3249 | 3250 | // invalidate before switching canvas, if needed. 3251 | if (canvas_changed) { 3252 | if ((view->flags & TFXI_VIEW_INVALIDATE) == TFXI_VIEW_INVALIDATE && tfx_glInvalidateFramebuffer) { 3253 | int offset = 0; 3254 | GLenum attachments[8]; 3255 | for (unsigned i = 0; i < canvas->allocated; i++) { 3256 | tfx_texture *attachment = &canvas->attachments[i]; 3257 | if (attachment->is_stencil && attachment->is_depth) { 3258 | attachments[i] = GL_DEPTH_STENCIL_ATTACHMENT; 3259 | } 3260 | else if (attachment->is_stencil) { 3261 | attachments[i] = GL_STENCIL_ATTACHMENT; 3262 | } 3263 | else if (attachment->is_depth) { 3264 | attachments[i] = GL_DEPTH_ATTACHMENT; 3265 | } 3266 | else { 3267 | attachments[i] = GL_COLOR_ATTACHMENT0 + offset; 3268 | offset += 1; 3269 | } 3270 | } 3271 | CHECK(tfx_glInvalidateFramebuffer(GL_FRAMEBUFFER, canvas->allocated, attachments)); 3272 | } 3273 | } 3274 | 3275 | if ((view->flags & TFXI_VIEW_FLUSH) == TFXI_VIEW_FLUSH) { 3276 | CHECK(tfx_glFlush()); 3277 | } 3278 | 3279 | int last_mip = (last_canvas && last_canvas->current_mip) ? last_canvas->current_mip : 0; 3280 | bool reset_mip = canvas_changed && last_mip != 0; 3281 | bool mip_changed = reset_mip || last_mip != view->canvas_layer; 3282 | // reset mipmap level range when done rendering, so sampling works. 3283 | if (reset_mip) { 3284 | int offset = 0; 3285 | for (unsigned i = 0; i < last_canvas->allocated; i++) { 3286 | tfx_texture *attachment = &last_canvas->attachments[i]; 3287 | CHECK(tfx_glBindTexture(GL_TEXTURE_2D, attachment->gl_ids[0])); 3288 | CHECK(tfx_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0)); 3289 | CHECK(tfx_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, attachment->mip_count-1)); 3290 | 3291 | GLenum attach = GL_DEPTH_ATTACHMENT; 3292 | if (attachment->is_stencil && attachment->is_depth) { 3293 | attach = GL_DEPTH_STENCIL_ATTACHMENT; 3294 | } 3295 | else if (attachment->is_stencil) { 3296 | attach = GL_STENCIL_ATTACHMENT; 3297 | } 3298 | else if (!attachment->is_depth && !attachment->is_stencil) { 3299 | attach = GL_COLOR_ATTACHMENT0 + offset; 3300 | offset += 1; 3301 | } 3302 | CHECK(tfx_glFramebufferTexture2D(GL_FRAMEBUFFER, attach, GL_TEXTURE_2D, attachment->gl_ids[0], 0)); 3303 | } 3304 | last_canvas->current_width = last_canvas->width; 3305 | last_canvas->current_height = last_canvas->height; 3306 | last_canvas->current_mip = 0; 3307 | } 3308 | 3309 | // resolve msaa if needed 3310 | if (last_canvas 3311 | && last_canvas->msaa 3312 | && (last_canvas->attachments[0].flags & TFX_TEXTURE_MSAA_SAMPLE) != TFX_TEXTURE_MSAA_SAMPLE 3313 | && ((canvas_changed && last_mip == 0) || (mip_changed && last_mip == 0)) 3314 | ) { 3315 | GLenum mask = 0; 3316 | for (unsigned i = 0; i < last_canvas->allocated; i++) { 3317 | tfx_texture *attach = &last_canvas->attachments[i]; 3318 | if (attach->is_stencil) { 3319 | mask |= GL_STENCIL_BUFFER_BIT; 3320 | } 3321 | if (attach->is_depth) { 3322 | mask |= GL_DEPTH_BUFFER_BIT; 3323 | } 3324 | if (!attach->is_depth && !attach->is_stencil) { 3325 | mask |= GL_COLOR_BUFFER_BIT; 3326 | } 3327 | } 3328 | CHECK(tfx_glBindFramebuffer(GL_DRAW_FRAMEBUFFER, last_canvas->gl_fbo[0])); 3329 | CHECK(tfx_glBindFramebuffer(GL_READ_FRAMEBUFFER, last_canvas->gl_fbo[1])); 3330 | CHECK(tfx_glBlitFramebuffer( 3331 | 0, 0, last_canvas->width, last_canvas->height, // src 3332 | 0, 0, last_canvas->width, last_canvas->height, // dst 3333 | mask, GL_NEAREST 3334 | )); 3335 | } 3336 | int nb = sb_count(view->blits); 3337 | stats.blits += nb; 3338 | 3339 | if (nb > 0) { 3340 | for (int b = 0; b < nb; b++) { 3341 | tfx_blit_op *blit = &view->blits[b]; 3342 | tfx_canvas *src = blit->source; 3343 | if (tfx_glCopyImageSubData) { 3344 | int argh = 0; 3345 | if (canvas->attachments[0].is_depth || canvas->attachments[0].is_stencil) { 3346 | argh = 1; 3347 | } 3348 | CHECK(tfx_glCopyImageSubData( 3349 | src->attachments[argh].gl_ids[0], GL_TEXTURE_2D, blit->source_mip, 3350 | blit->rect.x, blit->rect.y, 0, 3351 | canvas->attachments[0].gl_ids[0], GL_TEXTURE_2D, canvas->current_mip, 3352 | blit->rect.x, blit->rect.y, 0, 3353 | blit->rect.w, blit->rect.h, 1 3354 | )); 3355 | } else { 3356 | CHECK(tfx_glBindFramebuffer(GL_DRAW_FRAMEBUFFER, canvas->msaa ? canvas->gl_fbo[1] : canvas->gl_fbo[0])); 3357 | CHECK(tfx_glBindFramebuffer(GL_READ_FRAMEBUFFER, src->msaa ? src->gl_fbo[1] : src->gl_fbo[0])); 3358 | if (blit->source_mip != src->current_mip) { 3359 | // TODO: calculate correct dest size, update mip bindings 3360 | assert(0); 3361 | // CHECK(tfx_glFramebufferTexture2D(GL_FRAMEBUFFER, attach, GL_TEXTURE_2D, attachment->gl_ids[0], canvas->current_mip)); 3362 | } 3363 | CHECK(tfx_glBlitFramebuffer( 3364 | blit->rect.x, blit->rect.y, blit->rect.w, blit->rect.h, // src 3365 | blit->rect.x, blit->rect.y, blit->rect.w, blit->rect.h, // dst 3366 | blit->mask, GL_NEAREST 3367 | )); 3368 | } 3369 | } 3370 | } 3371 | 3372 | // run compute after blit so compute can rely on msaa being resolved first. 3373 | if (g_caps.compute && cd > 0) { 3374 | for (int i = 0; i < cd; i++) { 3375 | tfx_draw job = view->jobs[i]; 3376 | if (job.program != last_program) { 3377 | CHECK(tfx_glUseProgram(job.program)); 3378 | last_program = job.program; 3379 | } 3380 | 3381 | for (int j = 0; j < 8; j++) { 3382 | // make sure writing to image has finished before read 3383 | tfx_texture *tex = &job.textures[j]; 3384 | GLuint id = tex->gl_ids[tex->gl_idx]; 3385 | if (id != 0) { 3386 | static PFNGLBINDIMAGETEXTUREPROC tfx_glBindImageTexture = NULL; 3387 | if (!tfx_glBindImageTexture) { 3388 | tfx_glBindImageTexture = g_platform_data.gl_get_proc_address("glBindImageTexture"); 3389 | } 3390 | if (tex->dirty) { 3391 | CHECK(tfx_glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT)); 3392 | tex->dirty = false; 3393 | } 3394 | bool write = job.textures_write[j]; 3395 | if (write) { 3396 | tex->dirty = true; 3397 | } 3398 | tfx_texture_params *internal = (tfx_texture_params*)tex->internal; 3399 | GLenum fmt = internal->internal_format; 3400 | switch (fmt) { 3401 | case GL_DEPTH_COMPONENT16: fmt = GL_R16F; break; 3402 | case GL_DEPTH_COMPONENT24: assert(0); break; 3403 | case GL_DEPTH_COMPONENT32: fmt = GL_R32F; break; 3404 | default: break; 3405 | } 3406 | bool cube = (tex->flags & TFX_TEXTURE_CUBE) == TFX_TEXTURE_CUBE; 3407 | CHECK(tfx_glBindImageTexture(j, tex->gl_ids[tex->gl_idx], job.textures_mip[j], cube, 0, write ? GL_WRITE_ONLY : GL_READ_ONLY, fmt)); 3408 | } 3409 | if (job.ssbos[j].gl_id != 0) { 3410 | tfx_buffer *ssbo = &job.ssbos[j]; 3411 | if (ssbo->dirty) { 3412 | CHECK(tfx_glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT)); 3413 | ssbo->dirty = false; 3414 | } 3415 | if (job.ssbo_write[j]) { 3416 | ssbo->dirty = true; 3417 | } 3418 | CHECK(tfx_glBindBufferBase(GL_SHADER_STORAGE_BUFFER, j, job.ssbos[j].gl_id)); 3419 | } 3420 | else { 3421 | //CHECK(tfx_glBindBufferBase(GL_SHADER_STORAGE_BUFFER, j, 0)); 3422 | } 3423 | } 3424 | update_uniforms(&job); 3425 | CHECK(tfx_glDispatchCompute(job.threads_x, job.threads_y, job.threads_z)); 3426 | sb_free(job.uniforms); 3427 | job.uniforms = NULL; 3428 | } 3429 | sb_free(view->jobs); 3430 | view->jobs = NULL; 3431 | } 3432 | 3433 | // TODO: defer framebuffer creation 3434 | 3435 | if (canvas->allocated == 0 || nd == 0) { 3436 | continue; 3437 | } 3438 | 3439 | if (canvas->reconfigure) { 3440 | CHECK(tfx_glBindFramebuffer(GL_FRAMEBUFFER, canvas->gl_fbo[0])); 3441 | canvas_reconfigure(canvas, false); 3442 | canvas->reconfigure = false; 3443 | } 3444 | 3445 | // TODO: can't render to individual cube face mips with msaa this way 3446 | bool bind_msaa = canvas->msaa && view->canvas_layer <= 0; 3447 | CHECK(tfx_glBindFramebuffer(GL_FRAMEBUFFER, bind_msaa ? canvas->gl_fbo[1] : canvas->gl_fbo[0])); 3448 | 3449 | if (view->canvas_layer >= 0 && canvas->current_mip != view->canvas_layer && !canvas->cube) { 3450 | int offset = 0; 3451 | for (unsigned i = 0; i < canvas->allocated; i++) { 3452 | tfx_texture *attachment = &canvas->attachments[i]; 3453 | canvas->current_width = canvas->width; 3454 | canvas->current_height = canvas->height; 3455 | canvas->current_mip = view->canvas_layer; 3456 | for (int j = 0; j < canvas->current_mip; j++) { 3457 | canvas->current_width /= 2; 3458 | canvas->current_height /= 2; 3459 | canvas->current_width = canvas->current_width > 1 ? canvas->current_width : 1; 3460 | canvas->current_height = canvas->current_height > 1 ? canvas->current_height : 1; 3461 | } 3462 | GLenum attach = GL_DEPTH_ATTACHMENT; 3463 | if (attachment->is_depth && attachment->is_stencil) { 3464 | attach = GL_DEPTH_STENCIL_ATTACHMENT; 3465 | } 3466 | else if (attachment->is_stencil) { 3467 | attach = GL_STENCIL_ATTACHMENT; 3468 | } 3469 | else if (!attachment->is_depth && !attachment->is_stencil) { 3470 | attach = GL_COLOR_ATTACHMENT0 + offset; 3471 | offset += 1; 3472 | } 3473 | 3474 | assert(canvas->current_mip > 0); 3475 | CHECK(tfx_glFramebufferTexture2D(GL_FRAMEBUFFER, attach, GL_TEXTURE_2D, attachment->gl_ids[0], canvas->current_mip)); 3476 | 3477 | // bind next level for rendering but first restrict fetches only to previous level 3478 | CHECK(tfx_glBindTexture(GL_TEXTURE_2D, attachment->gl_ids[0])); 3479 | CHECK(tfx_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, canvas->current_mip-1)); 3480 | CHECK(tfx_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, canvas->current_mip-1)); 3481 | } 3482 | } 3483 | 3484 | if (view->viewport_count == 0) { 3485 | tfx_rect *vp = &view->viewports[0]; 3486 | vp->x = 0; 3487 | vp->y = 0; 3488 | vp->w = canvas->current_width; 3489 | vp->h = canvas->current_height; 3490 | view->viewport_count = 1; 3491 | } 3492 | 3493 | // TODO: render whole view multiple times if this is unavailable? 3494 | if (tfx_glViewportIndexedf) { 3495 | for (int v = 0; v < view->viewport_count; v++) { 3496 | tfx_rect *vp = &view->viewports[v]; 3497 | CHECK(tfx_glViewportIndexedf(v, (float)vp->x, (float)vp->y, (float)vp->w, (float)vp->h)); 3498 | } 3499 | } 3500 | else { 3501 | tfx_rect *vp = &view->viewports[0]; 3502 | CHECK(tfx_glViewport(vp->x, vp->y, vp->w, vp->h)); 3503 | } 3504 | 3505 | // TODO: caps flags for texture array support 3506 | if (view->canvas_layer < 0) { 3507 | assert(canvas->allocated <= 2); 3508 | for (unsigned i = 0; i < canvas->allocated; i++) { 3509 | tfx_texture *attachment = &canvas->attachments[i]; 3510 | // TODO: depth stencil 3511 | if (attachment->is_depth) { 3512 | CHECK(tfx_glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, attachment->gl_ids[0], 0)); 3513 | } 3514 | else { 3515 | CHECK(tfx_glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, attachment->gl_ids[0], 0)); 3516 | } 3517 | } 3518 | } 3519 | else if (canvas->cube) { 3520 | assert(canvas->allocated <= 2); 3521 | for (unsigned i = 0; i < canvas->allocated; i++) { 3522 | tfx_texture *attachment = &canvas->attachments[i]; 3523 | if (attachment->is_depth) { 3524 | CHECK(tfx_glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_POSITIVE_X + view->canvas_layer, attachment->gl_ids[0], 0)); 3525 | } 3526 | else { 3527 | CHECK(tfx_glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + view->canvas_layer, attachment->gl_ids[0], 0)); 3528 | } 3529 | } 3530 | } 3531 | 3532 | if (last_canvas && canvas != last_canvas && last_canvas->gl_fbo[0] != canvas->gl_fbo[0]) { 3533 | GLenum fmt = last_canvas->cube ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D; 3534 | if (last_canvas->attachments[0].depth > 1) { 3535 | assert(fmt != GL_TEXTURE_CUBE_MAP); // GL4 & not supported yet 3536 | fmt = GL_TEXTURE_2D_ARRAY; 3537 | } 3538 | for (unsigned i = 0; i < last_canvas->allocated; i++) { 3539 | tfx_texture *attachment = &last_canvas->attachments[i]; 3540 | if ((attachment->flags & TFX_TEXTURE_GEN_MIPS) != TFX_TEXTURE_GEN_MIPS) { 3541 | continue; 3542 | } 3543 | tfx_glBindTexture(fmt, attachment->gl_ids[attachment->gl_idx]); 3544 | tfx_glGenerateMipmap(fmt); 3545 | } 3546 | } 3547 | last_canvas = canvas; 3548 | 3549 | if (view->flags & TFXI_VIEW_SCISSOR) { 3550 | tfx_rect rect = view->scissor_rect; 3551 | CHECK(tfx_glEnable(GL_SCISSOR_TEST)); 3552 | CHECK(tfx_glScissor(rect.x, canvas->height - rect.y - rect.h, rect.w, rect.h)); 3553 | } 3554 | else { 3555 | CHECK(tfx_glDisable(GL_SCISSOR_TEST)); 3556 | } 3557 | 3558 | GLuint mask = 0; 3559 | if (view->flags & TFXI_VIEW_CLEAR_COLOR) { 3560 | mask |= GL_COLOR_BUFFER_BIT; 3561 | unsigned color = view->clear_color; 3562 | float c[] = { 3563 | ((color >> 24) & 0xff) / 255.0f, 3564 | ((color >> 16) & 0xff) / 255.0f, 3565 | ((color >> 8) & 0xff) / 255.0f, 3566 | ((color >> 0) & 0xff) / 255.0f 3567 | }; 3568 | CHECK(tfx_glClearColor(c[0], c[1], c[2], c[3])); 3569 | CHECK(tfx_glColorMask(true, true, true, true)); 3570 | } 3571 | 3572 | if (view->flags & TFXI_VIEW_CLEAR_DEPTH) { 3573 | mask |= GL_DEPTH_BUFFER_BIT; 3574 | CHECK(tfx_glClearDepthf(view->clear_depth)); 3575 | CHECK(tfx_glDepthMask(true)); 3576 | } 3577 | 3578 | if (mask != 0) { 3579 | CHECK(tfx_glClear(mask)); 3580 | } 3581 | 3582 | if (view->flags & TFXI_VIEW_DEPTH_TEST_MASK) { 3583 | CHECK(tfx_glEnable(GL_DEPTH_TEST)); 3584 | if (view->flags & TFXI_VIEW_DEPTH_TEST_LT) { 3585 | CHECK(tfx_glDepthFunc(GL_LEQUAL)); 3586 | } 3587 | else if (view->flags & TFXI_VIEW_DEPTH_TEST_GT) { 3588 | CHECK(tfx_glDepthFunc(GL_GEQUAL)); 3589 | } 3590 | else if (view->flags & TFXI_VIEW_DEPTH_TEST_EQ) { 3591 | CHECK(tfx_glDepthFunc(GL_EQUAL)); 3592 | } 3593 | } 3594 | else { 3595 | CHECK(tfx_glDisable(GL_DEPTH_TEST)); 3596 | } 3597 | 3598 | #define CHANGED(diff, mask) ((diff & mask) != 0) 3599 | 3600 | uint64_t last_flags = 0; 3601 | for (int i = 0; i < nd; i++) { 3602 | tfx_draw draw = view->draws[i]; 3603 | if (draw.program != last_program) { 3604 | CHECK(tfx_glUseProgram(draw.program)); 3605 | last_program = draw.program; 3606 | } 3607 | 3608 | // on first iteration of a pass, make sure to set everything. 3609 | if (i == 0) { 3610 | last_flags = ~draw.flags; 3611 | } 3612 | 3613 | // simple flag diff cuts total GL calls by approx 20% in testing 3614 | uint64_t flags_diff = draw.flags ^ last_flags; 3615 | last_flags = draw.flags; 3616 | 3617 | if (CHANGED(flags_diff, TFX_STATE_DEPTH_WRITE)) { 3618 | CHECK(tfx_glDepthMask((draw.flags & TFX_STATE_DEPTH_WRITE) == TFX_STATE_DEPTH_WRITE)); 3619 | } 3620 | 3621 | if (CHANGED(flags_diff, TFX_STATE_MSAA) && g_caps.multisample) { 3622 | if (draw.flags & TFX_STATE_MSAA) { 3623 | CHECK(tfx_glEnable(GL_MULTISAMPLE)); 3624 | } 3625 | else { 3626 | CHECK(tfx_glDisable(GL_MULTISAMPLE)); 3627 | } 3628 | } 3629 | 3630 | if (CHANGED(flags_diff, TFXI_STATE_CULL_MASK)) { 3631 | if (draw.flags & TFX_STATE_CULL_CW) { 3632 | CHECK(tfx_glEnable(GL_CULL_FACE)); 3633 | CHECK(tfx_glFrontFace(GL_CW)); 3634 | } 3635 | else if (draw.flags & TFX_STATE_CULL_CCW) { 3636 | CHECK(tfx_glEnable(GL_CULL_FACE)); 3637 | CHECK(tfx_glFrontFace(GL_CCW)); 3638 | } 3639 | else { 3640 | CHECK(tfx_glDisable(GL_CULL_FACE)); 3641 | } 3642 | } 3643 | 3644 | if (CHANGED(flags_diff, TFXI_STATE_BLEND_MASK)) { 3645 | if (draw.flags & TFXI_STATE_BLEND_MASK) { 3646 | CHECK(tfx_glEnable(GL_BLEND)); 3647 | if (draw.flags & TFX_STATE_BLEND_ALPHA) { 3648 | CHECK(tfx_glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); 3649 | } 3650 | } 3651 | else { 3652 | CHECK(tfx_glDisable(GL_BLEND)); 3653 | } 3654 | } 3655 | 3656 | if (CHANGED(flags_diff, TFX_STATE_RGB_WRITE) || CHANGED(flags_diff, TFX_STATE_ALPHA_WRITE)) { 3657 | bool write_rgb = (draw.flags & TFX_STATE_RGB_WRITE) == TFX_STATE_RGB_WRITE; 3658 | bool write_alpha = (draw.flags & TFX_STATE_ALPHA_WRITE) == TFX_STATE_ALPHA_WRITE; 3659 | CHECK(tfx_glColorMask(write_rgb, write_rgb, write_rgb, write_alpha)); 3660 | } 3661 | 3662 | if ((view->flags & TFXI_VIEW_SCISSOR) || draw.use_scissor) { 3663 | CHECK(tfx_glEnable(GL_SCISSOR_TEST)); 3664 | tfx_rect rect = view->scissor_rect; 3665 | if (draw.use_scissor) { 3666 | rect = draw.scissor_rect; 3667 | } 3668 | CHECK(tfx_glScissor(rect.x, canvas->height - rect.y - rect.h, rect.w, rect.h)); 3669 | } 3670 | else { 3671 | CHECK(tfx_glDisable(GL_SCISSOR_TEST)); 3672 | } 3673 | 3674 | update_uniforms(&draw); 3675 | 3676 | if (draw.callback != NULL) { 3677 | draw.callback(); 3678 | } 3679 | 3680 | if (!draw.use_vbo && !draw.use_ibo) { 3681 | sb_free(draw.uniforms); 3682 | draw.uniforms = NULL; 3683 | continue; 3684 | } 3685 | 3686 | GLenum mode = GL_TRIANGLES; 3687 | switch (draw.flags & TFXI_STATE_DRAW_MASK) { 3688 | case TFX_STATE_DRAW_POINTS: mode = GL_POINTS; break; 3689 | case TFX_STATE_DRAW_LINES: mode = GL_LINES; break; 3690 | case TFX_STATE_DRAW_LINE_STRIP: mode = GL_LINE_STRIP; break; 3691 | case TFX_STATE_DRAW_LINE_LOOP: mode = GL_LINE_LOOP; break; 3692 | case TFX_STATE_DRAW_TRI_STRIP: mode = GL_TRIANGLE_STRIP; break; 3693 | case TFX_STATE_DRAW_TRI_FAN: mode = GL_TRIANGLE_FAN; break; 3694 | default: break; // unspecified = triangles. 3695 | } 3696 | 3697 | // not available on gles 3698 | if (CHANGED(flags_diff, TFX_STATE_WIREFRAME) && !g_platform_data.use_gles) { 3699 | if (draw.flags & TFX_STATE_WIREFRAME) { 3700 | CHECK(tfx_glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)); 3701 | } 3702 | else { 3703 | CHECK(tfx_glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)); 3704 | } 3705 | } 3706 | 3707 | if (draw.use_vbo) { 3708 | GLuint vbo = draw.vbo.gl_id; 3709 | #ifdef TFX_DEBUG 3710 | assert(vbo != 0); 3711 | #endif 3712 | 3713 | if (draw.vbo.dirty && tfx_glMemoryBarrier) { 3714 | CHECK(tfx_glMemoryBarrier(GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT)); 3715 | draw.vbo.dirty = false; 3716 | } 3717 | 3718 | uint32_t va_offset = 0; 3719 | if (draw.use_tvb) { 3720 | draw.vbo.format = draw.tvb_fmt; 3721 | va_offset = draw.offset; 3722 | } 3723 | tfx_vertex_format *fmt = &draw.vbo.format; 3724 | assert(fmt != NULL); 3725 | assert(fmt->stride > 0); 3726 | 3727 | CHECK(tfx_glBindBuffer(GL_ARRAY_BUFFER, vbo)); 3728 | 3729 | int nc = fmt->count; 3730 | #ifdef TFX_DEBUG 3731 | assert(nc < 8); // the mask is only 8 bits 3732 | #endif 3733 | 3734 | int real = 0; 3735 | for (int i = 0; i < nc; i++) { 3736 | if ((fmt->component_mask & (1 << i)) == 0) { 3737 | continue; 3738 | } 3739 | tfx_vertex_component vc = fmt->components[i]; 3740 | GLenum gl_type = GL_FLOAT; 3741 | switch (vc.type) { 3742 | case TFX_TYPE_SKIP: continue; 3743 | case TFX_TYPE_UBYTE: gl_type = GL_UNSIGNED_BYTE; break; 3744 | case TFX_TYPE_BYTE: gl_type = GL_BYTE; break; 3745 | case TFX_TYPE_USHORT: gl_type = GL_UNSIGNED_SHORT; break; 3746 | case TFX_TYPE_SHORT: gl_type = GL_SHORT; break; 3747 | case TFX_TYPE_FLOAT: break; 3748 | default: assert(0); break; 3749 | } 3750 | if (i >= last_count) { 3751 | CHECK(tfx_glEnableVertexAttribArray(real)); 3752 | } 3753 | CHECK(tfx_glVertexAttribPointer(real, (GLint)vc.size, gl_type, vc.normalized, (GLsizei)fmt->stride, (GLvoid*)(vc.offset + va_offset))); 3754 | real += 1; 3755 | } 3756 | 3757 | last_count = last_count < 0 ? 0 : last_count; 3758 | nc = last_count - nc; 3759 | for (int i = 0; i <= nc; i++) { 3760 | CHECK(tfx_glDisableVertexAttribArray(last_count - i)); 3761 | } 3762 | last_count = real; 3763 | } 3764 | else if (last_count > 0) { 3765 | last_count = 0; 3766 | for (int i = 0; i < 8; i++) { 3767 | CHECK(tfx_glDisableVertexAttribArray(i)); 3768 | } 3769 | } 3770 | 3771 | GLuint bind_units[8]; 3772 | for (int i = 0; i < 8; i++) { 3773 | tfx_buffer *ssbo = &draw.ssbos[i]; 3774 | if (ssbo->gl_id != 0) { 3775 | if (ssbo->dirty) { 3776 | CHECK(tfx_glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT)); 3777 | ssbo->dirty = false; 3778 | } 3779 | if (draw.ssbo_write[i]) { 3780 | ssbo->dirty = true; 3781 | } 3782 | CHECK(tfx_glBindBufferBase(GL_SHADER_STORAGE_BUFFER, i, draw.ssbos[i].gl_id)); 3783 | } 3784 | 3785 | tfx_texture *tex = &draw.textures[i]; 3786 | bool msaa_sample = (tex->flags & TFX_TEXTURE_MSAA_SAMPLE) == TFX_TEXTURE_MSAA_SAMPLE; 3787 | GLuint idx = msaa_sample ? 1 : tex->gl_idx; 3788 | GLuint id = tex->gl_ids[idx]; 3789 | bind_units[i] = id; 3790 | if (!g_caps.multibind && id > 0) { 3791 | CHECK(tfx_glActiveTexture(GL_TEXTURE0 + i)); 3792 | 3793 | bool cube = (tex->flags & TFX_TEXTURE_CUBE) == TFX_TEXTURE_CUBE; 3794 | GLenum fmt = cube ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D; 3795 | if (tex->depth > 1) { 3796 | assert(fmt != GL_TEXTURE_CUBE_MAP); 3797 | fmt = GL_TEXTURE_2D_ARRAY; 3798 | } 3799 | if (msaa_sample) { 3800 | fmt = GL_TEXTURE_2D_MULTISAMPLE; 3801 | } 3802 | CHECK(tfx_glBindTexture(fmt, id)); 3803 | } 3804 | } 3805 | if (g_caps.multibind) { 3806 | CHECK(tfx_glBindTextures(0, 8, bind_units)); 3807 | } 3808 | 3809 | int instance_mul = view->instance_mul; 3810 | if (instance_mul == 0) { 3811 | instance_mul = view->viewport_count; 3812 | // canvas layer -1 indicates using layered rendering, multiply instances to suit layer count. 3813 | // for a cubemap array on multiple viewports, this really could be a lot! 3814 | if (view->canvas_layer < 0) { 3815 | if (canvas->cube) { 3816 | instance_mul *= 6; 3817 | } 3818 | if (canvas->attachments[0].depth > 1) { 3819 | instance_mul *= canvas->attachments[0].depth; 3820 | } 3821 | } 3822 | } 3823 | 3824 | if (draw.use_ibo) { 3825 | if (draw.ibo.dirty && tfx_glMemoryBarrier) { 3826 | CHECK(tfx_glMemoryBarrier(GL_ELEMENT_ARRAY_BARRIER_BIT)); 3827 | draw.ibo.dirty = false; 3828 | } 3829 | CHECK(tfx_glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, draw.ibo.gl_id)); 3830 | GLenum index_mode = GL_UNSIGNED_SHORT; 3831 | if ((draw.ibo.flags & TFX_BUFFER_INDEX_32) == TFX_BUFFER_INDEX_32) { 3832 | index_mode = GL_UNSIGNED_INT; 3833 | } 3834 | CHECK(tfx_glDrawElementsInstanced(mode, draw.indices, index_mode, (GLvoid*)draw.offset, 1*instance_mul)); 3835 | } 3836 | else { 3837 | CHECK(tfx_glDrawArraysInstanced(mode, 0, (GLsizei)draw.indices, 1*instance_mul)); 3838 | } 3839 | 3840 | sb_free(draw.uniforms); 3841 | draw.uniforms = NULL; 3842 | } 3843 | 3844 | #undef CHANGED 3845 | 3846 | sb_free(view->jobs); 3847 | view->jobs = NULL; 3848 | 3849 | sb_free(view->draws); 3850 | view->draws = NULL; 3851 | 3852 | sb_free(view->blits); 3853 | view->blits = NULL; 3854 | } 3855 | 3856 | pop_group(); 3857 | 3858 | if (use_timers) { 3859 | // record the finishing time so we can figure out the last view timing 3860 | CHECK(tfx_glQueryCounter(g_timers[VIEW_MAX + g_timer_offset], GL_TIMESTAMP)); 3861 | } 3862 | 3863 | if (use_timers && stats.num_timings > 0) { 3864 | int idx = VIEW_MAX + next_offset; 3865 | GLuint result_available = 0; 3866 | CHECK(tfx_glGetQueryObjectuiv(g_timers[idx], GL_QUERY_RESULT_AVAILABLE, &result_available)); 3867 | if (result_available) { 3868 | GLuint64 result = 0; 3869 | CHECK(tfx_glGetQueryObjectui64v(g_timers[idx], GL_QUERY_RESULT, &result)); 3870 | stats.timings[stats.num_timings-1].time = result - last_result; 3871 | } 3872 | } 3873 | 3874 | g_timer_offset = next_offset; 3875 | 3876 | reset(); 3877 | 3878 | tvb_reset(); 3879 | 3880 | sb_free(g_back.uniforms); 3881 | g_back.uniforms = NULL; 3882 | 3883 | g_back.ub_cursor = g_back.uniform_buffer; 3884 | 3885 | CHECK(tfx_glDisable(GL_SCISSOR_TEST)); 3886 | CHECK(tfx_glColorMask(true, true, true, true)); 3887 | 3888 | if (tfx_glDeleteVertexArrays) { 3889 | CHECK(tfx_glDeleteVertexArrays(1, &vao)); 3890 | } 3891 | 3892 | // shift buffers to avoid stalls 3893 | tfx_buffer tmp = g_transient_buffer.buffers[0]; 3894 | for (int i = 0; i < TFX_TRANSIENT_BUFFER_COUNT - 1; i++) { 3895 | g_transient_buffer.buffers[i] = g_transient_buffer.buffers[i+1]; 3896 | } 3897 | g_transient_buffer.buffers[TFX_TRANSIENT_BUFFER_COUNT-1] = tmp; 3898 | 3899 | if ((g_flags & TFX_RESET_DEBUG_OVERLAY_STATS) == TFX_RESET_DEBUG_OVERLAY_STATS) { 3900 | int row = 0; 3901 | 3902 | uint16_t color[2] = { 0x140f, 0x160f }; 3903 | 3904 | const char *str = tfx_sprintf("Draws: %5d", stats.draws); 3905 | int lrow = row; row++; // avoid warning from -Wunsequenced 3906 | tfx_debug_print(lrow, 0, color[row % 2], 0, str); 3907 | free((char*)str); 3908 | 3909 | lrow = row; row++; 3910 | str = tfx_sprintf("Blits: %5d", stats.blits); 3911 | tfx_debug_print(lrow, 0, color[row % 2], 0, str); 3912 | free((char*)str); 3913 | 3914 | int max_width = 0; 3915 | for (unsigned i = 0; i < stats.num_timings; i++) { 3916 | int len = strnlen(stats.timings[i].name, 100); 3917 | if (len > max_width) { 3918 | max_width = len; 3919 | } 3920 | } 3921 | uint64_t sum = 0; 3922 | for (unsigned i = 0; i < stats.num_timings; i++, row++) { 3923 | sum += stats.timings[i].time; 3924 | str = stats.timings[i].name; 3925 | int width = 5 + max_width; 3926 | tfx_debug_print(row, width - strnlen(str, 100), color[row % 2], 0, str); 3927 | 3928 | str = tfx_sprintf("%3d", stats.timings[i].id); 3929 | tfx_debug_print(row, 0, color[row % 2], 0, str); 3930 | free((char*)str); 3931 | 3932 | str = tfx_sprintf("%dus", stats.timings[i].time / 1000); 3933 | tfx_debug_print(row, 8 + width - strnlen(str, 20), color[row % 2], 0, str); 3934 | free((char*)str); 3935 | } 3936 | tfx_debug_print(row, 0, 0x100f, 0, "Total:"); 3937 | str = tfx_sprintf("%dus", sum / 1000); 3938 | tfx_debug_print(row, 13 + max_width - strnlen(str, 20), 0x100f, 0, str); 3939 | free((char*)str); 3940 | } 3941 | 3942 | return stats; 3943 | } 3944 | #undef MAX_VIEW 3945 | #undef CHECK 3946 | 3947 | #undef TFX_INFO 3948 | #undef TFX_WARN 3949 | #undef TFX_ERROR 3950 | #undef TFX_FATAL 3951 | 3952 | #ifdef _MSC_VER 3953 | #pragma warning( pop ) 3954 | #endif 3955 | 3956 | #ifdef __cplusplus 3957 | } 3958 | #endif 3959 | 3960 | #endif // TFX_IMPLEMENTATION 3961 | -------------------------------------------------------------------------------- /tinyfx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | typedef enum tfx_buffer_flags { 12 | TFX_BUFFER_NONE = 0, 13 | // for index buffers: use 32-bit instead of 16-bit indices. 14 | TFX_BUFFER_INDEX_32 = 1 << 0, 15 | // updated regularly (once per game tick) 16 | TFX_BUFFER_MUTABLE = 1 << 1, 17 | // temporary (updated many times per frame) 18 | // TFX_BUFFER_STREAM = 1 << 2 19 | } tfx_buffer_flags; 20 | 21 | typedef enum tfx_depth_test { 22 | TFX_DEPTH_TEST_NONE = 0, 23 | TFX_DEPTH_TEST_LT, 24 | TFX_DEPTH_TEST_GT, 25 | TFX_DEPTH_TEST_EQ 26 | } tfx_depth_test; 27 | 28 | #define TFX_INVALID_BUFFER tfx_buffer { 0 } 29 | #define TFX_INVALID_TRANSIENT_BUFFER tfx_transient_buffer { 0 } 30 | 31 | // draw state 32 | enum { 33 | // cull modes 34 | TFX_STATE_CULL_CW = 1 << 0, 35 | TFX_STATE_CULL_CCW = 1 << 1, 36 | 37 | // depth write 38 | TFX_STATE_DEPTH_WRITE = 1 << 2, 39 | TFX_STATE_RGB_WRITE = 1 << 3, 40 | TFX_STATE_ALPHA_WRITE = 1 << 4, 41 | 42 | // blending 43 | TFX_STATE_BLEND_ALPHA = 1 << 5, 44 | 45 | // primitive modes 46 | TFX_STATE_DRAW_POINTS = 1 << 6, 47 | TFX_STATE_DRAW_LINES = 1 << 7, 48 | TFX_STATE_DRAW_LINE_STRIP = 1 << 8, 49 | TFX_STATE_DRAW_LINE_LOOP = 1 << 9, 50 | TFX_STATE_DRAW_TRI_STRIP = 1 << 10, 51 | TFX_STATE_DRAW_TRI_FAN = 1 << 11, 52 | 53 | // misc state 54 | TFX_STATE_MSAA = 1 << 12, 55 | TFX_STATE_WIREFRAME = 1 << 13, 56 | 57 | TFX_STATE_DEFAULT = 0 58 | | TFX_STATE_CULL_CCW 59 | | TFX_STATE_MSAA 60 | | TFX_STATE_DEPTH_WRITE 61 | | TFX_STATE_RGB_WRITE 62 | | TFX_STATE_ALPHA_WRITE 63 | | TFX_STATE_BLEND_ALPHA 64 | }; 65 | 66 | enum { 67 | TFX_CUBE_MAP_POSITIVE_X = 0, 68 | TFX_CUBE_MAP_NEGATIVE_X = 1, 69 | TFX_CUBE_MAP_POSITIVE_Y = 2, 70 | TFX_CUBE_MAP_NEGATIVE_Y = 3, 71 | TFX_CUBE_MAP_POSITIVE_Z = 4, 72 | TFX_CUBE_MAP_NEGATIVE_Z = 5 73 | }; 74 | 75 | enum { 76 | TFX_TEXTURE_FILTER_POINT = 1 << 0, 77 | TFX_TEXTURE_FILTER_LINEAR = 1 << 1, 78 | //TFX_TEXTURE_FILTER_ANISOTROPIC = 1 << 2, 79 | TFX_TEXTURE_CPU_WRITABLE = 1 << 3, 80 | // TFX_TEXTURE_GPU_WRITABLE = 1 << 4, 81 | TFX_TEXTURE_GEN_MIPS = 1 << 5, 82 | TFX_TEXTURE_RESERVE_MIPS = 1 << 6, 83 | TFX_TEXTURE_CUBE = 1 << 7, 84 | TFX_TEXTURE_MSAA_SAMPLE = 1 << 8, 85 | TFX_TEXTURE_MSAA_X2 = 1 << 9, 86 | TFX_TEXTURE_MSAA_X4 = 1 << 10, 87 | TFX_TEXTURE_EXTERNAL = 1 << 11 88 | }; 89 | 90 | typedef enum tfx_reset_flags { 91 | TFX_RESET_NONE = 0, 92 | TFX_RESET_MAX_ANISOTROPY = 1 << 0, 93 | TFX_RESET_REPORT_GPU_TIMINGS = 1 << 1, 94 | // a basic text/image overlay for debugging. 95 | // be aware that it's pretty slow, since it just plots pixels on cpu. 96 | // you probably don't want to leave this on if you're not using it! 97 | TFX_RESET_DEBUG_OVERLAY = 1 << 2, 98 | TFX_RESET_DEBUG_OVERLAY_STATS = 1 << 3 99 | // TFX_RESET_VR 100 | } tfx_reset_flags; 101 | 102 | typedef enum tfx_view_flags { 103 | TFX_VIEW_NONE = 0, 104 | TFX_VIEW_INVALIDATE = 1 << 0, 105 | TFX_VIEW_FLUSH = 1 << 1, 106 | TFX_VIEW_SORT_SEQUENTIAL = 1 << 2, 107 | TFX_VIEW_DEFAULT = TFX_VIEW_SORT_SEQUENTIAL 108 | } tfx_view_flags; 109 | 110 | typedef enum tfx_format { 111 | // color only 112 | TFX_FORMAT_RGB565 = 0, 113 | TFX_FORMAT_RGBA8, 114 | 115 | TFX_FORMAT_SRGB8, 116 | TFX_FORMAT_SRGB8_A8, 117 | 118 | TFX_FORMAT_RGB10A2, 119 | TFX_FORMAT_RG11B10F, 120 | TFX_FORMAT_RGB16F, 121 | TFX_FORMAT_RGBA16F, 122 | 123 | // color + depth 124 | TFX_FORMAT_RGB565_D16, 125 | TFX_FORMAT_RGBA8_D16, 126 | TFX_FORMAT_RGBA8_D24, 127 | 128 | // TFX_FORMAT_RGB10A2_D16, 129 | // TFX_FORMAT_RG11B10F_D16, 130 | // TFX_FORMAT_RGBA16F_D16, 131 | 132 | // single channel 133 | TFX_FORMAT_R16F, 134 | TFX_FORMAT_R32UI, 135 | TFX_FORMAT_R32F, 136 | 137 | // two channels 138 | TFX_FORMAT_RG16F, 139 | TFX_FORMAT_RG32F, 140 | 141 | // depth only 142 | TFX_FORMAT_D16, 143 | TFX_FORMAT_D24, 144 | TFX_FORMAT_D32, 145 | TFX_FORMAT_D32F, 146 | //TFX_FORMAT_D24_S8 147 | } tfx_format; 148 | 149 | typedef unsigned tfx_program; 150 | 151 | typedef enum tfx_uniform_type { 152 | TFX_UNIFORM_INT = 0, 153 | TFX_UNIFORM_FLOAT, 154 | TFX_UNIFORM_VEC2, 155 | TFX_UNIFORM_VEC3, 156 | TFX_UNIFORM_VEC4, 157 | TFX_UNIFORM_MAT2, 158 | TFX_UNIFORM_MAT3, 159 | TFX_UNIFORM_MAT4 160 | } tfx_uniform_type; 161 | 162 | typedef enum tfx_severity { 163 | TFX_SEVERITY_INFO, 164 | TFX_SEVERITY_WARNING, 165 | TFX_SEVERITY_ERROR, 166 | TFX_SEVERITY_FATAL 167 | } tfx_severity; 168 | 169 | typedef struct tfx_platform_data { 170 | bool use_gles; 171 | int context_version; 172 | void* (*gl_get_proc_address)(const char*); 173 | void(*info_log)(const char* msg, tfx_severity level); 174 | } tfx_platform_data; 175 | 176 | typedef struct tfx_uniform { 177 | union { 178 | float *fdata; 179 | int *idata; 180 | uint8_t *data; 181 | }; 182 | const char *name; 183 | tfx_uniform_type type; 184 | int count; 185 | int last_count; 186 | size_t size; 187 | } tfx_uniform; 188 | 189 | typedef struct tfx_texture { 190 | unsigned gl_ids[2]; 191 | unsigned gl_msaa_id; 192 | unsigned gl_idx, gl_count; 193 | uint16_t width; 194 | uint16_t height; 195 | uint16_t depth; 196 | uint16_t mip_count; 197 | tfx_format format; 198 | bool is_depth; 199 | bool is_stencil; 200 | bool dirty; 201 | uint16_t flags, _pad0; 202 | void *internal; 203 | } tfx_texture; 204 | 205 | typedef struct tfx_canvas { 206 | // 0 = normal, 1 = msaa 207 | unsigned gl_fbo[2]; 208 | tfx_texture attachments[8]; 209 | uint32_t allocated; 210 | uint16_t width; 211 | uint16_t height; 212 | uint16_t current_width; 213 | uint16_t current_height; 214 | int current_mip; 215 | //bool mipmaps; 216 | bool msaa; 217 | bool cube; 218 | bool own_attachments; 219 | bool reconfigure; 220 | } tfx_canvas; 221 | 222 | typedef enum tfx_component_type { 223 | TFX_TYPE_FLOAT = 0, 224 | TFX_TYPE_BYTE, 225 | TFX_TYPE_UBYTE, 226 | TFX_TYPE_SHORT, 227 | TFX_TYPE_USHORT, 228 | TFX_TYPE_SKIP, 229 | } tfx_component_type; 230 | 231 | typedef struct tfx_vertex_component { 232 | size_t offset; 233 | size_t size; 234 | bool normalized; 235 | tfx_component_type type; 236 | } tfx_vertex_component; 237 | 238 | typedef struct tfx_vertex_format { 239 | // limit to 8, since we only have an 8 bit mask 240 | tfx_vertex_component components[8]; 241 | uint8_t count, component_mask, _pad0[2]; 242 | size_t stride; 243 | } tfx_vertex_format; 244 | 245 | typedef struct tfx_buffer { 246 | unsigned gl_id; 247 | bool dirty; 248 | bool has_format; 249 | tfx_buffer_flags flags; 250 | tfx_vertex_format format; 251 | void *internal; 252 | } tfx_buffer; 253 | 254 | typedef struct tfx_transient_buffer { 255 | bool has_format; 256 | tfx_vertex_format format; 257 | void *data; 258 | uint16_t num; 259 | uint32_t offset; 260 | } tfx_transient_buffer; 261 | 262 | typedef void (*tfx_draw_callback)(void); 263 | 264 | typedef struct tfx_timing_info { 265 | uint64_t time; 266 | uint8_t id, _pad0[3]; 267 | const char *name; 268 | } tfx_timing_info; 269 | 270 | typedef struct tfx_stats { 271 | uint32_t draws; 272 | uint32_t blits; 273 | uint32_t num_timings; 274 | tfx_timing_info *timings; 275 | } tfx_stats; 276 | 277 | typedef struct tfx_caps { 278 | bool compute; 279 | bool float_canvas; 280 | bool multisample; 281 | bool debug_marker; 282 | bool debug_output; 283 | bool memory_info; 284 | bool instancing; 285 | bool seamless_cubemap; 286 | bool anisotropic_filtering; 287 | bool multibind; 288 | } tfx_caps; 289 | 290 | // TODO 291 | // #define TFX_API __attribute__ ((visibility("default"))) 292 | #define TFX_API 293 | 294 | // bg_fg: 2x 8 bit palette colors. standard palette matches vga. 295 | // bg_fg = (bg << 8) | fg, assuming little endian 296 | TFX_API void tfx_debug_print(const int baserow, const int basecol, const uint16_t bg_fg, const int auto_wrap, const char *str); 297 | TFX_API void tfx_debug_blit_rgba(const int x, const int y, const int w, const int h, const uint32_t *pixels); 298 | // each pixel is an 8-bit palette index (standard palette matches vga, shared with text) 299 | TFX_API void tfx_debug_blit_pal(const int x, const int y, const int w, const int h, const uint8_t *pixels); 300 | // note: doesn't copy! make sure to keep this palette in memory yourself. 301 | // pass NULL to reset to default palette. expects ABGR input. 302 | TFX_API void tfx_debug_set_palette(const uint32_t *palette); 303 | 304 | TFX_API void tfx_set_platform_data(tfx_platform_data pd); 305 | 306 | TFX_API tfx_caps tfx_get_caps(); 307 | TFX_API void tfx_dump_caps(); 308 | TFX_API void tfx_reset(uint16_t width, uint16_t height, tfx_reset_flags flags); 309 | TFX_API void tfx_shutdown(); 310 | 311 | TFX_API tfx_vertex_format tfx_vertex_format_start(); 312 | TFX_API void tfx_vertex_format_add(tfx_vertex_format *fmt, uint8_t slot, size_t count, bool normalized, tfx_component_type type); 313 | TFX_API void tfx_vertex_format_end(tfx_vertex_format *fmt); 314 | TFX_API size_t tfx_vertex_format_offset(tfx_vertex_format *fmt, uint8_t slot); 315 | 316 | TFX_API uint32_t tfx_transient_buffer_get_available(tfx_vertex_format *fmt); 317 | TFX_API tfx_transient_buffer tfx_transient_buffer_new(tfx_vertex_format *fmt, uint16_t num_verts); 318 | 319 | TFX_API tfx_buffer tfx_buffer_new(const void *data, size_t size, tfx_vertex_format *format, tfx_buffer_flags flags); 320 | TFX_API void tfx_buffer_update(tfx_buffer *buf, const void *data, uint32_t offset, uint32_t size); 321 | TFX_API void tfx_buffer_free(tfx_buffer *buf); 322 | 323 | TFX_API tfx_texture tfx_texture_new(uint16_t w, uint16_t h, uint16_t layers, const void *data, tfx_format format, uint16_t flags); 324 | TFX_API void tfx_texture_update(tfx_texture *tex, const void *data); 325 | TFX_API void tfx_texture_free(tfx_texture *tex); 326 | TFX_API tfx_texture tfx_get_texture(tfx_canvas *canvas, uint8_t index); 327 | 328 | TFX_API tfx_canvas tfx_canvas_new(uint16_t w, uint16_t h, tfx_format format, uint16_t flags); 329 | TFX_API void tfx_canvas_free(tfx_canvas *c); 330 | TFX_API tfx_canvas tfx_canvas_attachments_new(bool claim_attachments, int count, tfx_texture *attachments); 331 | 332 | TFX_API void tfx_view_set_name(uint8_t id, const char *name); 333 | TFX_API void tfx_view_set_canvas(uint8_t id, tfx_canvas *canvas, int layer); 334 | TFX_API void tfx_view_set_flags(uint8_t id, tfx_view_flags flags); 335 | TFX_API void tfx_view_set_clear_color(uint8_t id, unsigned color); 336 | TFX_API void tfx_view_set_clear_depth(uint8_t id, float depth); 337 | TFX_API void tfx_view_set_depth_test(uint8_t id, tfx_depth_test mode); 338 | TFX_API void tfx_view_set_scissor(uint8_t id, uint16_t x, uint16_t y, uint16_t w, uint16_t h); 339 | // order: xywh, in pixels 340 | TFX_API void tfx_view_set_viewports(uint8_t id, int count, uint16_t **viewports); 341 | TFX_API void tfx_view_set_instance_mul(uint8_t id, unsigned factor); 342 | TFX_API tfx_canvas *tfx_view_get_canvas(uint8_t id); 343 | TFX_API uint16_t tfx_view_get_width(uint8_t id); 344 | TFX_API uint16_t tfx_view_get_height(uint8_t id); 345 | TFX_API void tfx_view_get_dimensions(uint8_t id, uint16_t *w, uint16_t *h); 346 | // TFX_API void tfx_view_set_transform(uint8_t id, float *view, float *proj_l, float *proj_r); 347 | 348 | // you may pass -1 for attrib_count to use a null-terminated list for attribs 349 | TFX_API tfx_program tfx_program_new(const char *vss, const char *fss, const char *attribs[], const int attrib_count); 350 | // you may pass -1 for attrib_count to use a null-terminated list for attribs 351 | TFX_API tfx_program tfx_program_len_new(const char *vss, const int _vs_len, const char *fss, const int _fs_len, const char *attribs[], const int attrib_count); 352 | // you may pass -1 for attrib_count to use a null-terminated list for attribs 353 | TFX_API tfx_program tfx_program_gs_len_new(const char *_gss, const int _gs_len, const char *_vss, const int _vs_len, const char *_fss, const int _fs_len, const char *attribs[], const int attrib_count); 354 | // you may pass -1 for attrib_count to use a null-terminated list for attribs 355 | TFX_API tfx_program tfx_program_gs_new(const char *gss, const char *vss, const char *fss, const char *attribs[], const int attrib_count); 356 | TFX_API tfx_program tfx_program_cs_len_new(const char *css, const int _cs_len); 357 | TFX_API tfx_program tfx_program_cs_new(const char *css); 358 | // TODO: add tfx_program_free(tfx_program). they are currently cleaned up with tfx_shutdown. 359 | 360 | TFX_API tfx_uniform tfx_uniform_new(const char *name, tfx_uniform_type type, int count); 361 | 362 | // TFX_API void tfx_set_transform(float *mtx, uint8_t count); 363 | TFX_API void tfx_set_transient_buffer(tfx_transient_buffer tb); 364 | // pass -1 to update maximum uniform size 365 | TFX_API void tfx_set_uniform(tfx_uniform *uniform, const float *data, const int count); 366 | // pass -1 to update maximum uniform size 367 | TFX_API void tfx_set_uniform_int(tfx_uniform *uniform, const int *data, const int count); 368 | TFX_API void tfx_set_callback(tfx_draw_callback cb); 369 | TFX_API void tfx_set_state(uint64_t flags); 370 | TFX_API void tfx_set_scissor(uint16_t x, uint16_t y, uint16_t w, uint16_t h); 371 | TFX_API void tfx_set_texture(tfx_uniform *uniform, tfx_texture *tex, uint8_t slot); 372 | TFX_API void tfx_set_buffer(tfx_buffer *buf, uint8_t slot, bool write); 373 | TFX_API void tfx_set_image(tfx_uniform *uniform, tfx_texture *tex, uint8_t slot, uint8_t mip, bool write); 374 | TFX_API void tfx_set_vertices(tfx_buffer *vbo, int count); 375 | TFX_API void tfx_set_indices(tfx_buffer *ibo, int count, int offset); 376 | TFX_API void tfx_dispatch(uint8_t id, tfx_program program, uint32_t x, uint32_t y, uint32_t z); 377 | // TFX_API void tfx_submit_ordered(uint8_t id, tfx_program program, uint32_t depth, bool retain); 378 | TFX_API void tfx_submit(uint8_t id, tfx_program program, bool retain); 379 | // submit an empty draw. useful for using draw callbacks and ensuring views are processed. 380 | TFX_API void tfx_touch(uint8_t id); 381 | 382 | TFX_API void tfx_blit(uint8_t src, uint8_t dst, uint16_t x, uint16_t y, uint16_t w, uint16_t h, int mip); 383 | 384 | TFX_API tfx_stats tfx_frame(); 385 | 386 | #undef TFX_API 387 | 388 | #ifdef __cplusplus 389 | } 390 | #endif 391 | -------------------------------------------------------------------------------- /tinyfx.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace tfx { 8 | struct VertexFormat { 9 | tfx_vertex_format fmt; 10 | VertexFormat() { 11 | this->fmt = tfx_vertex_format_start(); 12 | } 13 | inline void add(size_t count, uint8_t slot, bool normalized = false, tfx_component_type type = TFX_TYPE_FLOAT) { 14 | tfx_vertex_format_add(&this->fmt, slot, count, normalized, type); 15 | } 16 | inline void end() { 17 | tfx_vertex_format_end(&this->fmt); 18 | } 19 | inline size_t offset(uint8_t slot) { 20 | return tfx_vertex_format_offset(&this->fmt, slot); 21 | } 22 | }; 23 | 24 | struct Buffer { 25 | tfx_buffer buffer; 26 | // Buffer(std::vector &data, VertexFormat fmt, tfx_buffer_usage usage = TFX_USAGE_STATIC) { 27 | // this->buffer = tfx_buffer_new(&data[0], data.size()*sizeof(T), &fmt.fmt, usage); 28 | // } 29 | Buffer(tfx_buffer &buf) { 30 | this->buffer = buf; 31 | } 32 | }; 33 | 34 | struct TransientBuffer { 35 | tfx_transient_buffer tvb; 36 | TransientBuffer(VertexFormat &fmt, uint16_t num) { 37 | tvb = tfx_transient_buffer_new(&fmt.fmt, num); 38 | } 39 | }; 40 | 41 | struct Uniform { 42 | tfx_uniform uniform; 43 | Uniform(const char *name, tfx_uniform_type type, int count = 1) { 44 | this->uniform = tfx_uniform_new(name, type, count); 45 | } 46 | }; 47 | 48 | struct Canvas { 49 | tfx_canvas canvas; 50 | Canvas(uint16_t w, uint16_t h, tfx_format format = TFX_FORMAT_RGBA8_D16, uint16_t flags = TFX_TEXTURE_FILTER_POINT) { 51 | this->canvas = tfx_canvas_new(w, h, format, flags); 52 | } 53 | }; 54 | 55 | struct View { 56 | uint8_t id; 57 | View(uint8_t _id) { 58 | this->id = _id; 59 | } 60 | inline void set_canvas(Canvas *canvas, int layer = 0) { 61 | tfx_view_set_canvas(this->id, &canvas->canvas, layer); 62 | } 63 | inline void set_clear_color(int color = 0x000000ff) { 64 | tfx_view_set_clear_color(this->id, color); 65 | } 66 | inline void set_clear_depth(float depth = 1.0f) { 67 | tfx_view_set_clear_depth(this->id, depth); 68 | } 69 | inline void set_depth_test(tfx_depth_test mode = TFX_DEPTH_TEST_NONE) { 70 | tfx_view_set_depth_test(this->id, mode); 71 | } 72 | inline void set_scissor(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { 73 | tfx_view_set_scissor(this->id, x, y, w, h); 74 | } 75 | // inline void set_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { 76 | // tfx_view_set_rect(this->id, x, y, w, h); 77 | // } 78 | inline uint16_t get_width() { 79 | return tfx_view_get_width(this->id); 80 | } 81 | inline uint16_t get_height() { 82 | return tfx_view_get_width(this->id); 83 | } 84 | inline void get_dimensions(uint16_t *w, uint16_t *h) { 85 | tfx_view_get_dimensions(this->id, w, h); 86 | } 87 | inline void touch() { 88 | tfx_touch(this->id); 89 | } 90 | }; 91 | 92 | struct Texture { 93 | tfx_texture texture; 94 | Texture(uint16_t w, uint16_t h, uint16_t layers = 1, void *data = NULL, tfx_format format = TFX_FORMAT_RGBA8, uint16_t flags = TFX_TEXTURE_FILTER_LINEAR) { 95 | this->texture = tfx_texture_new(w, h, layers, data, format, flags); 96 | } 97 | }; 98 | 99 | struct Program { 100 | tfx_program program; 101 | Program(std::string vss, std::string fss, const char *attribs[]) { 102 | this->program = tfx_program_new(vss.c_str(), fss.c_str(), attribs); 103 | } 104 | }; 105 | 106 | inline void dump_caps() { 107 | tfx_dump_caps(); 108 | } 109 | inline tfx_caps get_caps() { 110 | return tfx_get_caps(); 111 | } 112 | inline uint32_t transient_buffer_get_available(const VertexFormat &fmt) { 113 | return tfx_transient_buffer_get_available((tfx_vertex_format*)&fmt.fmt); 114 | } 115 | inline void touch(uint8_t id) { 116 | tfx_touch(id); 117 | } 118 | inline void reset(uint16_t width, uint16_t height, tfx_reset_flags flags = TFX_RESET_NONE) { 119 | tfx_reset(width, height, flags); 120 | } 121 | inline void shutdown() { 122 | tfx_shutdown(); 123 | } 124 | inline tfx_stats frame() { 125 | return tfx_frame(); 126 | } 127 | inline void set_uniform(Uniform &uniform, float data) { 128 | float tmp = data; 129 | tfx_set_uniform(&uniform.uniform, &tmp, -1); 130 | } 131 | inline void set_uniform(Uniform &uniform, float *data) { 132 | tfx_set_uniform(&uniform.uniform, data, -1); 133 | } 134 | inline void set_texture(Uniform &uniform, Texture &texture, uint8_t slot) { 135 | tfx_set_texture(&uniform.uniform, &texture.texture, slot); 136 | } 137 | inline void set_callback(tfx_draw_callback cb) { 138 | tfx_set_callback(cb); 139 | } 140 | inline void set_state(uint64_t flags) { 141 | tfx_set_state(flags); 142 | } 143 | inline void set_buffer(Buffer &buf, uint8_t slot, bool write = false) { 144 | tfx_set_buffer(&buf.buffer, slot, write); 145 | } 146 | inline void set_transient_buffer(TransientBuffer &tvb) { 147 | tfx_set_transient_buffer(tvb.tvb); 148 | } 149 | inline void set_vertices(Buffer &vbo, int count = 0) { 150 | tfx_set_vertices(&vbo.buffer, count); 151 | } 152 | inline void set_indices(Buffer &ibo, int count, int offset = 0) { 153 | tfx_set_indices(&ibo.buffer, count, offset); 154 | } 155 | inline void dispatch(uint8_t id, Program &program, uint32_t x, uint32_t y, uint32_t z) { 156 | tfx_dispatch(id, program.program, x, y, z); 157 | } 158 | inline void dispatch(View &view, Program &program, uint32_t x, uint32_t y, uint32_t z) { 159 | tfx_dispatch(view.id, program.program, x, y, z); 160 | } 161 | // inline void submit(uint8_t id, Program &program, uint32_t depth = 0, bool retain = false) { 162 | // tfx_submit_ordered(id, program.program, depth, retain); 163 | // } 164 | inline void submit(uint8_t id, Program &program, bool retain = false) { 165 | tfx_submit(id, program.program, retain); 166 | } 167 | inline void submit(View &view, Program &program, bool retain = false) { 168 | tfx_submit(view.id, program.program, retain); 169 | } 170 | // inline void blit(tfx_view *src, tfx_view *dst, uint16_t x, uint16_t y, uint16_t w, uint16_t h); 171 | 172 | } // tfx 173 | --------------------------------------------------------------------------------