removing old sample, replaced with python/shader_deblocking
diff --git a/shader_deblocking/README.md b/shader_deblocking/README.md deleted file mode 100644 index bcd0004..0000000 --- a/shader_deblocking/README.md +++ /dev/null
@@ -1,135 +0,0 @@ -# Python+GLSL Shader Deblocking Sample - -*Block boundaries are predictable.* - -This sample demonstrates how to use a simple pixel shader to greatly reduce -ASTC texture block artifacts, which can be quite noticeable when the block size goes -beyond roughly 6x6. The basic idea: instead of always sampling the texture using -a single tap, you instead sample the texture either one time or X times with a simple low pass filter, -depending on whether or not the sample location is near a block edge. The multiple filter taps around -the center sample blur across block boundaries of ASTC compressed textures. There are two independent filters, for horizontal and vertical block boundaries. - -The example shader is compatible with mipmapping, bilinear filtering, trilinear filtering etc. and is temporally stable. The -shader smoothly lerps between no filtering and edge filtering, and is mipmap-aware by using the pixel shader derivative instructions. Crucially, the block lattice is evaluated in the *effective mip space*, not in base texture space, which is why it's mipmap-aware. The Python sample shows either a textured quad or a cube, with various controls to move the object, rotate the cube, toggle the deblocking shader on/off, trilinear off/on, etc. - -It was written to be as simple as possible. The shader's filter coefficients were picked for more blurring vs. our CPU deblocker to demonstrate the effect, but they are easily tuned. The shader could easily be more optimized (most inner block texels don't need any filtering, but we don't exploit this in the shader with a conditional yet). The idea is compatible with other texture formats with noticeable block artifacts, such as BC1 or ETC1, but 4x4 blocks are so tiny it may be a wash. - -It's also possible to add adaptivity to this shader, so it doesn't blindly blur across sharp edges - like we do while -CPU deblocking before transcoding to BC7 or other LDR texture formats. It's also possible -to add deblocking filter awareness to our ASTC/XUASTC/etc. encoders. - -The shader computes the mipmap LOD index assuming an entire mip chain is present (i.e. down to 1x1 texels). If the mipmap chain is incomplete, the developer should clamp the computed mipmap LOD index in the shader. We'll show this in the next update of the sample. - -Note: We're amazed the GPU hardware vendors haven't implemented this feature directly in silicon yet. It's extremely useful, even necessary at the largest ASTC block sizes. -This is a form of "GPU texture compression-aware shading" or "GPU format-informed reconstruction". - -## Running the Sample - -You'll need these Python dependencies to run it: -``` -pip install numpy Pillow glfw PyOpenGL -``` - -You may also want "PyOpenGL_accelerate", and under Linux you may need to also install the system package "libglfw3". We developed this sample under Windows 11. - -Run: - -``` -python testbed.py shader.glsl 12 12 flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_0_face_0_layer_0000.png flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_1_face_0_layer_0000.png flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_2_face_0_layer_0000.png flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_3_face_0_layer_0000.png flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_4_face_0_layer_0000.png -``` - -Depending on your setup you may need to use `python3`, or `py -3.12` etc. - -Keys: `1` toggles deblocking shader on/off (it defaults to disabled), `2` toggles edges vis (only shows when deblocking is enabled), arrow keys move the object, `W`/`S` keys: forward/backward, `A`/`D`/`Q`/`E`: yaw/pitch, `C`: toggle cube vs. quad. (See the source code remarks for more.) - -The shader can be easily simplified to sample the texture less by using less taps. The current shader uses a total of 9 taps, but 5 are possible. - -Many variations and optimizations of this basic idea are possible. *Now shader engineers can directly impact memory consumption.* The better your deblocking shaders are tuned or your specific content, the bigger the ASTC block size you can ship. ASTC texture deblocking pixel shader engineering is now a memory optimization skill. - -A simpler, [faster 5 tap filter variant is here](https://pastebin.com/jsF6nUZg). (This shader isn't quite compatible with the currently checked in Python sample, as it uses a new `maxLod` uniform to limit the max fetched mipmap level.) In practice, this simple shader usually looks just as good on most textures/images. ` textureQueryLod()` and `textureSize()` could also be used (with some API's) to simplify the shader further. We're continuing to refine and tune this shader. - ---- - -## Performance - -The deblocking shader itself can be put inside a dynamic `if` conditional, so the extra ALU/texture ops only kick in near block edges (which are the minority of samples at ASTC 12x12). The extra sample taps are spatially always near the center sample, so they'll hit the texture cache most of the time. In texture bandwidth bound rendering scenarios (quite common on mobile platforms), the extra ALU ops for deblocking likely come for "free". - -*Another perspective: The alternative to not deblocking is ~2x-8x more GPU memory bandwidth (and increased download size) to use smaller ASTC block sizes which have less noticeable block artifacts.* - ---- - -## Screenshots - ASTC 12x12 Block Size - -**Disabled:** - - -**Enabled:** - - ---- - -**Disabled:** - - -**Enabled:** - - ---- - -**Disabled:** - - -**Enabled:** - - ---- - -**Block Edge Computation Visualization:** - - - -These screenshots show how the pixel shader computes texture block boundaries in effective mipmap space. To see this visualization, press '1' to enable deblocking, then '2' to enable block edge visualization. Only white areas in this visualization are modified by this shader, leaving the inner block texels unmodified. - -**Obviously, it's crucial that the block size passed into the shader via constants exactly matches the ASTC texture's block size, or the filtering applied won't align with the actual ASTC block artifacts.** - ---- - -## Usage and Controls - -The sample either renders a single textured quad or a cube. Press 'C' to toggle between the quad and the cube. The '1' key toggles shader deblocking (off by default). The '2' key enables edge visualization, which only works when deblocking is enabled. - -Other keys can be used to move around the quad, rotate the cube etc.: - -``` -Usage: - python testbed.py shader.glsl block_w block_h mip0.png mip1.png [mip2.png ...] - block_w, block_h: Block size in texels (e.g. 8 8 for 8x8 DCT blocks) - -Controls: - Arrows Move quad left/right/up/down - W / S Move closer / farther - A / D Rotate yaw (cube mode) - Q / E Rotate pitch (cube mode) - C Toggle cube / quad mode - B Bilinear filtering - T Trilinear filtering - P Point filtering - R Reload shader - 1 Toggle deblocking shader off/on - 2 Toggle edge visualization (only when deblocking active) - 3-4 Toggle shader const0.x/y/z/w (0 <-> 1) - 5-8 Toggle shader const1.x/y/z/w (0 <-> 1) - Space Reset to initial state - Esc Quit -``` - ---- - -## Credits - -The included sunflower image is in the CC0/Public Domain, and was downloaded from here: - -https://www.publicdomainpictures.net/en/view-image.php?image=756601&picture=large-yellow-sunflower - -"License: CC0 Public Domain - Lynn Greyling has released this “Large Yellow Sunflower” image under Public Domain license." -
diff --git a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_0_face_0_layer_0000.png b/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_0_face_0_layer_0000.png deleted file mode 100644 index 5551f9b..0000000 --- a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_0_face_0_layer_0000.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_10_face_0_layer_0000.png b/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_10_face_0_layer_0000.png deleted file mode 100644 index a3c9bd2..0000000 --- a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_10_face_0_layer_0000.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_1_face_0_layer_0000.png b/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_1_face_0_layer_0000.png deleted file mode 100644 index f9ff90a..0000000 --- a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_1_face_0_layer_0000.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_2_face_0_layer_0000.png b/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_2_face_0_layer_0000.png deleted file mode 100644 index fd28127..0000000 --- a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_2_face_0_layer_0000.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_3_face_0_layer_0000.png b/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_3_face_0_layer_0000.png deleted file mode 100644 index 51287c8..0000000 --- a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_3_face_0_layer_0000.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_4_face_0_layer_0000.png b/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_4_face_0_layer_0000.png deleted file mode 100644 index 1db85df..0000000 --- a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_4_face_0_layer_0000.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_5_face_0_layer_0000.png b/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_5_face_0_layer_0000.png deleted file mode 100644 index 00e5a97..0000000 --- a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_5_face_0_layer_0000.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_6_face_0_layer_0000.png b/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_6_face_0_layer_0000.png deleted file mode 100644 index e9c9b16..0000000 --- a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_6_face_0_layer_0000.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_7_face_0_layer_0000.png b/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_7_face_0_layer_0000.png deleted file mode 100644 index fc9eccd..0000000 --- a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_7_face_0_layer_0000.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_8_face_0_layer_0000.png b/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_8_face_0_layer_0000.png deleted file mode 100644 index c9efb6c..0000000 --- a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_8_face_0_layer_0000.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_9_face_0_layer_0000.png b/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_9_face_0_layer_0000.png deleted file mode 100644 index d328ff1..0000000 --- a/shader_deblocking/flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_9_face_0_layer_0000.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/run.bat b/shader_deblocking/run.bat deleted file mode 100644 index 342dc2f..0000000 --- a/shader_deblocking/run.bat +++ /dev/null
@@ -1 +0,0 @@ -py -3.12 testbed.py shader.glsl 12 12 flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_0_face_0_layer_0000.png flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_1_face_0_layer_0000.png flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_2_face_0_layer_0000.png flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_3_face_0_layer_0000.png flower_unpacked_rgb_ASTC_LDR_12X12_RGBA_level_4_face_0_layer_0000.png
diff --git a/shader_deblocking/screenshots/1_off.png b/shader_deblocking/screenshots/1_off.png deleted file mode 100644 index 1f04f2c..0000000 --- a/shader_deblocking/screenshots/1_off.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/screenshots/1_on.png b/shader_deblocking/screenshots/1_on.png deleted file mode 100644 index e2d8e24..0000000 --- a/shader_deblocking/screenshots/1_on.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/screenshots/2_off.png b/shader_deblocking/screenshots/2_off.png deleted file mode 100644 index bc88fbd..0000000 --- a/shader_deblocking/screenshots/2_off.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/screenshots/2_on.png b/shader_deblocking/screenshots/2_on.png deleted file mode 100644 index 61315f7..0000000 --- a/shader_deblocking/screenshots/2_on.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/screenshots/3_off.png b/shader_deblocking/screenshots/3_off.png deleted file mode 100644 index 2d5a4f3..0000000 --- a/shader_deblocking/screenshots/3_off.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/screenshots/3_on.png b/shader_deblocking/screenshots/3_on.png deleted file mode 100644 index 5ba5559..0000000 --- a/shader_deblocking/screenshots/3_on.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/screenshots/edge_vis.png b/shader_deblocking/screenshots/edge_vis.png deleted file mode 100644 index 7553330..0000000 --- a/shader_deblocking/screenshots/edge_vis.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/screenshots/edge_vis2.png b/shader_deblocking/screenshots/edge_vis2.png deleted file mode 100644 index 0e18c83..0000000 --- a/shader_deblocking/screenshots/edge_vis2.png +++ /dev/null Binary files differ
diff --git a/shader_deblocking/shader.glsl b/shader_deblocking/shader.glsl deleted file mode 100644 index 837c686..0000000 --- a/shader_deblocking/shader.glsl +++ /dev/null
@@ -1,97 +0,0 @@ -#vertex -#version 330 core - -layout(location = 0) in vec3 aPos; -layout(location = 1) in vec2 aUV; - -uniform mat4 mvp; - -out vec2 vUV; - -void main() { - vUV = aUV; - gl_Position = mvp * vec4(aPos, 1.0); -} - -#fragment -#version 330 core - -uniform sampler2D tex; -uniform vec4 texSize; // Base mip dimensions (mip 0) -uniform vec4 const0; // User constant 0 (keys 1-4 toggle x,y,z,w) -uniform vec4 const1; // User constant 1 (keys 5-8 toggle x,y,z,w) - -in vec2 vUV; -out vec4 fragColor; - -void main() -{ - vec2 blockSize = vec2(texSize.z, texSize.w); - - vec2 du = dFdx(vUV); - vec2 dv = dFdy(vUV); - float rho = max(length(du * texSize.xy), length(dv * texSize.xy)); - float lod = max(0.0, log2(max(rho, 1e-8))); // lod index - float mipScale = exp2(floor(lod + .5)); // 2^lod mipmap scale, snaps to dominant mipmap - - vec2 texDim = vec2(texSize.x, texSize.y); - vec2 texelStep = 1.0 / texDim; - vec2 texelPos = (vUV * texDim) / mipScale; - vec2 blockPos = mod(texelPos, blockSize); - - vec3 color; - color = texture(tex, vUV).rgb; - - if (const0.x > 0.5) - { - float falloff = 2.0; - - float leftProx = 1.0 - clamp(blockPos.x / falloff, 0.0, 1.0); - float rightProx = 1.0 - clamp((blockSize.x - 1.0 - blockPos.x) / falloff, 0.0, 1.0); - float topProx = 1.0 - clamp(blockPos.y / falloff, 0.0, 1.0); - float bottomProx = 1.0 - clamp((blockSize.y - 1.0 - blockPos.y) / falloff, 0.0, 1.0); - - float horizWeight = max(leftProx, rightProx); - float vertWeight = max(topProx, bottomProx); - float edgeWeight = max(horizWeight, vertWeight); // overall proximity - - vec3 c0 = color; //texture2D(tex, vUV).rgb; - - vec3 l2 = texture2D(tex, vUV - vec2(2 * texelStep.x * mipScale, 0.0)).rgb; - vec3 l1 = texture2D(tex, vUV - vec2(texelStep.x * mipScale, 0.0)).rgb; - vec3 r1 = texture2D(tex, vUV + vec2(texelStep.x * mipScale, 0.0)).rgb; - vec3 r2 = texture2D(tex, vUV + vec2(2 * texelStep.x * mipScale, 0.0)).rgb; - - vec3 u2 = texture2D(tex, vUV - vec2(0.0, 2 * texelStep.y * mipScale)).rgb; - vec3 u1 = texture2D(tex, vUV - vec2(0.0, texelStep.y * mipScale)).rgb; - vec3 d1 = texture2D(tex, vUV + vec2(0.0, texelStep.y * mipScale)).rgb; - vec3 d2 = texture2D(tex, vUV + vec2(0.0, 2 * texelStep.y * mipScale)).rgb; - - //vec3 filteredH = (l2 + 2 * l1 + 3 * c0 + 2 * r1 + r2) / 9.0; - //vec3 filteredV = (u2 + 2 * u1 + 3 * c0 + 2 * d1 + d2) / 9.0; - - vec3 filteredH = (l2 + 2 * l1 + 2 * c0 + 2 * r1 + r2) / 8.0; - vec3 filteredV = (u2 + 2 * u1 + 2 * c0 + 2 * d1 + d2) / 8.0; - - float smoothH = 1.0; - float smoothV = 1.0; - - if (edgeWeight > 0.0) - { - vec3 horizColor = mix(c0, filteredH, smoothH * horizWeight); - vec3 vertColor = mix(c0, filteredV, smoothV * vertWeight); - - float totalW = horizWeight + vertWeight; - if (totalW > 0.0) - color = (horizColor * horizWeight + vertColor * vertWeight) / totalW; - } - - // block edge vis - if (const0.y > 0.5) - { - color = vec3(edgeWeight, edgeWeight, edgeWeight); - } - } - - fragColor = vec4(color, 1.0); -}
diff --git a/shader_deblocking/testbed.py b/shader_deblocking/testbed.py deleted file mode 100644 index a29d2e6..0000000 --- a/shader_deblocking/testbed.py +++ /dev/null
@@ -1,861 +0,0 @@ -#!/usr/bin/env python3 -""" -Mipmap Compatible Texture Sampling Deblocking Shader Testbed -Copyright (C) 2026 Binomial LLC. -LICENSE: Apache 2.0 - -Usage: - python testbed.py shader.glsl block_w block_h mip0.png mip1.png [mip2.png ...] - block_w, block_h: Block size in texels (e.g. 8 8 for 8x8 DCT blocks) - -Controls: - Arrows Move quad left/right/up/down - W / S Move closer / farther - A / D Rotate yaw (cube mode) - Q / E Rotate pitch (cube mode) - C Toggle cube / quad mode - B Bilinear filtering - T Trilinear filtering - P Point filtering - R Reload shader - 1 Toggle deblocking shader off/on - 2 Toggle edge visualization (only when deblocking active) - 3-4 Toggle shader const0.x/y/z/w (0 <-> 1) - 5-8 Toggle shader const1.x/y/z/w (0 <-> 1) - Space Reset to initial state - Esc Quit -""" - -import sys, os, importlib.util -print("=== DIAG ===") -print("exe:", sys.executable) -print("ver:", sys.version) -print("cwd:", os.getcwd()) -print("glfw spec:", importlib.util.find_spec("glfw")) -print("OpenGL spec:", importlib.util.find_spec("OpenGL")) -print("============") - -import sys -import ctypes -import numpy as np -from PIL import Image, ImageDraw, ImageFont -from pathlib import Path - -import glfw -from OpenGL.GL import * - - -# ----------------------------------------------------------------------------- -# Globals -# ----------------------------------------------------------------------------- -WINDOW_WIDTH = 1280 -WINDOW_HEIGHT = 720 -FOV_DEGREES = 90.0 -Z_MIN = .40 -Z_MAX = -50.0 -Z_SPEED = 2.0 -XY_SPEED = 1.5 -ROT_SPEED = 90.0 # degrees per second -# Block size (set from command line) -BLOCK_WIDTH = 12 -BLOCK_HEIGHT = 12 - -g_state = { - 'x': 0.0, - 'y': 0.0, - 'z': -3.0, - 'yaw': 0.0, - 'pitch': 0.0, - 'mode': 'QUAD', # 'QUAD' or 'CUBE' - 'filter_mode': 'BILINEAR', - 'shader_path': None, - 'program': None, - 'texture': None, - 'tex_size': (0, 0), - 'quad_vao': None, - 'cube_vao': None, - 'cube_index_count': 0, - 'debug_vao': None, - 'debug_texture': None, - 'debug_dirty': True, - 'last_time': 0.0, - 'const0': [0.0, 0.0, 0.0, 0.0], - 'const1': [0.0, 0.0, 0.0, 0.0], -} -INIT_X = 0.0 -INIT_Y = 0.0 -INIT_Z = -3.0 -INIT_YAW = 0.0 -INIT_PITCH = 0.0 -INIT_CONST0 = [0.0, 0.0, 0.0, 0.0] -INIT_CONST1 = [0.0, 0.0, 0.0, 0.0] - - -# ----------------------------------------------------------------------------- -# Shader Loading -# ----------------------------------------------------------------------------- -def parse_shader_file(path): - """Parse shader file with #vertex and #fragment markers.""" - text = Path(path).read_text() - - vertex_src = None - fragment_src = None - - parts = text.split('#vertex') - if len(parts) < 2: - print(f"ERROR: No #vertex marker found in {path}") - return None, None - - rest = parts[1] - frag_parts = rest.split('#fragment') - if len(frag_parts) < 2: - print(f"ERROR: No #fragment marker found in {path}") - return None, None - - vertex_src = frag_parts[0].strip() - fragment_src = frag_parts[1].strip() - - return vertex_src, fragment_src - - -def compile_shader(source, shader_type): - """Compile a shader, return handle or None on error.""" - shader = glCreateShader(shader_type) - glShaderSource(shader, source) - glCompileShader(shader) - - if glGetShaderiv(shader, GL_COMPILE_STATUS) != GL_TRUE: - error = glGetShaderInfoLog(shader) - if isinstance(error, bytes): - error = error.decode('utf-8') - type_name = "VERTEX" if shader_type == GL_VERTEX_SHADER else "FRAGMENT" - print(f"{type_name} SHADER ERROR:\n{error}") - glDeleteShader(shader) - return None - - return shader - - -def link_program(vertex_shader, fragment_shader): - """Link shaders into program, return handle or None on error.""" - program = glCreateProgram() - glAttachShader(program, vertex_shader) - glAttachShader(program, fragment_shader) - glLinkProgram(program) - - if glGetProgramiv(program, GL_LINK_STATUS) != GL_TRUE: - error = glGetProgramInfoLog(program) - if isinstance(error, bytes): - error = error.decode('utf-8') - print(f"LINK ERROR:\n{error}") - glDeleteProgram(program) - return None - - return program - - -def load_shader(path): - """Load, compile, and link shader from file. Returns program or None.""" - print(f"Loading shader: {path}") - - vertex_src, fragment_src = parse_shader_file(path) - if vertex_src is None or fragment_src is None: - return None - - vertex_shader = compile_shader(vertex_src, GL_VERTEX_SHADER) - if vertex_shader is None: - return None - - fragment_shader = compile_shader(fragment_src, GL_FRAGMENT_SHADER) - if fragment_shader is None: - glDeleteShader(vertex_shader) - return None - - program = link_program(vertex_shader, fragment_shader) - - glDeleteShader(vertex_shader) - glDeleteShader(fragment_shader) - - if program: - print("Shader compiled successfully.") - - return program - - -def reload_shader(): - """Attempt to reload shader. Keep old one if failed.""" - new_program = load_shader(g_state['shader_path']) - if new_program is not None: - if g_state['program'] is not None: - glDeleteProgram(g_state['program']) - g_state['program'] = new_program - else: - print("Shader reload failed, keeping previous shader.") - - -# ----------------------------------------------------------------------------- -# Texture Loading -# ----------------------------------------------------------------------------- -def load_mipmap_texture(paths): - """Load PNG files as mipmap levels. Returns texture handle and base size.""" - images = [] - - for i, path in enumerate(paths): - img = Image.open(path).convert('RGBA') - images.append(img) - print(f"Loaded mip {i}: {path} ({img.width}x{img.height})") - - # Validate dimensions - for i in range(1, len(images)): - expected_w = images[i - 1].width // 2 - expected_h = images[i - 1].height // 2 - actual_w = images[i].width - actual_h = images[i].height - - if actual_w != expected_w or actual_h != expected_h: - print(f"ERROR: Mip {i} should be {expected_w}x{expected_h}, got {actual_w}x{actual_h}") - sys.exit(1) - - # Create texture - texture = glGenTextures(1) - glBindTexture(GL_TEXTURE_2D, texture) - - # Upload each mip level - for level, img in enumerate(images): - data = np.array(img, dtype=np.uint8) - glTexImage2D( - GL_TEXTURE_2D, level, GL_RGBA8, - img.width, img.height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, data - ) - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, len(images) - 1) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) - - base_size = (images[0].width, images[0].height) - return texture, base_size - - -def set_filter_mode(mode): - """Set texture filtering mode.""" - glBindTexture(GL_TEXTURE_2D, g_state['texture']) - - if mode == 'BILINEAR': - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - elif mode == 'TRILINEAR': - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - else: # POINT - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) - - g_state['filter_mode'] = mode - g_state['debug_dirty'] = True - - -# ----------------------------------------------------------------------------- -# Geometry -# ----------------------------------------------------------------------------- -def create_quad(aspect_ratio): - """Create a quad VAO centered at origin with given aspect ratio.""" - # Normalize so longest dimension is 1.0 - if aspect_ratio >= 1.0: - half_w = 1.0 - half_h = 1.0 / aspect_ratio - else: - half_w = aspect_ratio - half_h = 1.0 - - # Position (x, y, z) + UV (u, v) - vertices = np.array([ - -half_w, -half_h, 0.0, 0.0, 1.0, - half_w, -half_h, 0.0, 1.0, 1.0, - half_w, half_h, 0.0, 1.0, 0.0, - -half_w, half_h, 0.0, 0.0, 0.0, - ], dtype=np.float32) - - indices = np.array([0, 1, 2, 0, 2, 3], dtype=np.uint32) - - vao = glGenVertexArrays(1) - vbo = glGenBuffers(1) - ebo = glGenBuffers(1) - - glBindVertexArray(vao) - - glBindBuffer(GL_ARRAY_BUFFER, vbo) - glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW) - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) - glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices, GL_STATIC_DRAW) - - # Position attribute (location 0) - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 20, ctypes.c_void_p(0)) - glEnableVertexAttribArray(0) - - # UV attribute (location 1) - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 20, ctypes.c_void_p(12)) - glEnableVertexAttribArray(1) - - glBindVertexArray(0) - - return vao - - -def create_cube(size=1.0): - """Create a textured cube VAO centered at origin.""" - h = size / 2.0 - - # Each face: 4 vertices with position (x,y,z) + UV (u,v) - # Front face (z = +h) - front = [ - -h, -h, h, 0.0, 1.0, - h, -h, h, 1.0, 1.0, - h, h, h, 1.0, 0.0, - -h, h, h, 0.0, 0.0, - ] - # Back face (z = -h) - back = [ - h, -h, -h, 0.0, 1.0, - -h, -h, -h, 1.0, 1.0, - -h, h, -h, 1.0, 0.0, - h, h, -h, 0.0, 0.0, - ] - # Right face (x = +h) - right = [ - h, -h, h, 0.0, 1.0, - h, -h, -h, 1.0, 1.0, - h, h, -h, 1.0, 0.0, - h, h, h, 0.0, 0.0, - ] - # Left face (x = -h) - left = [ - -h, -h, -h, 0.0, 1.0, - -h, -h, h, 1.0, 1.0, - -h, h, h, 1.0, 0.0, - -h, h, -h, 0.0, 0.0, - ] - # Top face (y = +h) - top = [ - -h, h, h, 0.0, 1.0, - h, h, h, 1.0, 1.0, - h, h, -h, 1.0, 0.0, - -h, h, -h, 0.0, 0.0, - ] - # Bottom face (y = -h) - bottom = [ - -h, -h, -h, 0.0, 1.0, - h, -h, -h, 1.0, 1.0, - h, -h, h, 1.0, 0.0, - -h, -h, h, 0.0, 0.0, - ] - - vertices = np.array(front + back + right + left + top + bottom, dtype=np.float32) - - # 6 faces, each with 2 triangles (6 indices per face) - indices = [] - for i in range(6): - base = i * 4 - indices.extend([base, base+1, base+2, base, base+2, base+3]) - indices = np.array(indices, dtype=np.uint32) - - vao = glGenVertexArrays(1) - vbo = glGenBuffers(1) - ebo = glGenBuffers(1) - - glBindVertexArray(vao) - - glBindBuffer(GL_ARRAY_BUFFER, vbo) - glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW) - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) - glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices, GL_STATIC_DRAW) - - # Position attribute (location 0) - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 20, ctypes.c_void_p(0)) - glEnableVertexAttribArray(0) - - # UV attribute (location 1) - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 20, ctypes.c_void_p(12)) - glEnableVertexAttribArray(1) - - glBindVertexArray(0) - - return vao, len(indices) - - -def create_debug_quad(): - """Create a screen-space quad for debug text overlay.""" - # Screen-space quad at top-left - # NDC: x=-1 is left, y=1 is top - w = 680.0 / WINDOW_WIDTH * 2.0 - h = 60.0 / WINDOW_HEIGHT * 2.0 - - vertices = np.array([ - -1.0, 1.0, 0.0, 0.0, - -1.0 + w, 1.0, 1.0, 0.0, - -1.0 + w, 1.0 - h, 1.0, 1.0, - -1.0, 1.0 - h, 0.0, 1.0, - ], dtype=np.float32) - - indices = np.array([0, 1, 2, 0, 2, 3], dtype=np.uint32) - - vao = glGenVertexArrays(1) - vbo = glGenBuffers(1) - ebo = glGenBuffers(1) - - glBindVertexArray(vao) - - glBindBuffer(GL_ARRAY_BUFFER, vbo) - glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW) - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo) - glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.nbytes, indices, GL_STATIC_DRAW) - - # Position attribute (location 0) - xy only - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 16, ctypes.c_void_p(0)) - glEnableVertexAttribArray(0) - - # UV attribute (location 1) - glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 16, ctypes.c_void_p(8)) - glEnableVertexAttribArray(1) - - glBindVertexArray(0) - - return vao - - -# ----------------------------------------------------------------------------- -# Debug Text -# ----------------------------------------------------------------------------- -DEBUG_VERTEX = """ -#version 330 core -layout(location = 0) in vec2 aPos; -layout(location = 1) in vec2 aUV; -out vec2 vUV; -void main() { - vUV = aUV; - gl_Position = vec4(aPos, 0.0, 1.0); -} -""" - -DEBUG_FRAGMENT = """ -#version 330 core -uniform sampler2D tex; -in vec2 vUV; -out vec4 fragColor; -void main() { - fragColor = texture(tex, vUV); -} -""" - -g_debug_program = None - - -def init_debug_rendering(): - """Initialize debug text rendering resources.""" - global g_debug_program - - vs = compile_shader(DEBUG_VERTEX, GL_VERTEX_SHADER) - fs = compile_shader(DEBUG_FRAGMENT, GL_FRAGMENT_SHADER) - - if vs is None or fs is None: - print("ERROR: Failed to compile debug shaders") - if vs: - glDeleteShader(vs) - if fs: - glDeleteShader(fs) - return - - g_debug_program = link_program(vs, fs) - glDeleteShader(vs) - glDeleteShader(fs) - - if g_debug_program is None: - print("ERROR: Failed to link debug program") - return - - g_state['debug_vao'] = create_debug_quad() - g_state['debug_texture'] = glGenTextures(1) - - -def update_debug_text(): - """Render debug text to texture.""" - if not g_state['debug_dirty']: - return - - c0 = g_state['const0'] - c1 = g_state['const1'] - - # Build status lines - lines = [ - f"Mode:{g_state['mode']:4s} Filter:{g_state['filter_mode']:9s} Block:{BLOCK_WIDTH}x{BLOCK_HEIGHT} Deblock: [{int(c0[0])}{int(c0[1])}{int(c0[2])}{int(c0[3])}][{int(c1[0])}{int(c1[1])}{int(c1[2])}{int(c1[3])}]", - f"X:{g_state['x']:+5.1f} Y:{g_state['y']:+5.1f} Z:{g_state['z']:5.1f} Yaw:{g_state['yaw']:+6.1f} Pitch:{g_state['pitch']:+6.1f}", - "Arrows:move, W/S:zoom, A/D:yaw, Q/E:pitch, C:cube, B/T/P:filter, 1=deblocking toggle, 2=edge vis, R:reload, Space:reset", - ] - - img = Image.new('RGBA', (680, 60), (0, 0, 0, 180)) - draw = ImageDraw.Draw(img) - - try: - font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf", 14) - except: - font = ImageFont.load_default() - - y = 4 - for line in lines: - draw.text((6, y), line, fill=(255, 255, 255, 255), font=font) - y += 18 - - data = np.array(img, dtype=np.uint8) - - glBindTexture(GL_TEXTURE_2D, g_state['debug_texture']) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, img.width, img.height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, data) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - - g_state['debug_dirty'] = False - - -def draw_debug_text(): - """Draw debug text overlay.""" - if g_debug_program is None: - return - - update_debug_text() - - glEnable(GL_BLEND) - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - glDisable(GL_DEPTH_TEST) - - glUseProgram(g_debug_program) - glBindTexture(GL_TEXTURE_2D, g_state['debug_texture']) - glBindVertexArray(g_state['debug_vao']) - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, None) - - glEnable(GL_DEPTH_TEST) - glDisable(GL_BLEND) - - -# ----------------------------------------------------------------------------- -# Math -# ----------------------------------------------------------------------------- -def perspective_matrix(fov_deg, aspect, near, far): - """Create perspective projection matrix.""" - fov_rad = np.radians(fov_deg) - f = 1.0 / np.tan(fov_rad / 2.0) - - m = np.zeros((4, 4), dtype=np.float32) - m[0, 0] = f / aspect - m[1, 1] = f - m[2, 2] = (far + near) / (near - far) - m[2, 3] = (2 * far * near) / (near - far) - m[3, 2] = -1.0 - - return m - - -def translation_matrix(x, y, z): - """Create translation matrix.""" - m = np.eye(4, dtype=np.float32) - m[0, 3] = x - m[1, 3] = y - m[2, 3] = z - return m - - -def rotation_matrix_y(deg): - """Create rotation matrix around Y axis (yaw).""" - rad = np.radians(deg) - c, s = np.cos(rad), np.sin(rad) - m = np.eye(4, dtype=np.float32) - m[0, 0] = c - m[0, 2] = s - m[2, 0] = -s - m[2, 2] = c - return m - - -def rotation_matrix_x(deg): - """Create rotation matrix around X axis (pitch).""" - rad = np.radians(deg) - c, s = np.cos(rad), np.sin(rad) - m = np.eye(4, dtype=np.float32) - m[1, 1] = c - m[1, 2] = -s - m[2, 1] = s - m[2, 2] = c - return m - - -# ----------------------------------------------------------------------------- -# Input -# ----------------------------------------------------------------------------- -def framebuffer_size_callback(window, width, height): - """Handle window resize.""" - global WINDOW_WIDTH, WINDOW_HEIGHT - WINDOW_WIDTH = width - WINDOW_HEIGHT = height - glViewport(0, 0, width, height) - g_state['debug_dirty'] = True - - -def key_callback(window, key, scancode, action, mods): - if action == glfw.PRESS: - if key == glfw.KEY_ESCAPE: - glfw.set_window_should_close(window, True) - elif key == glfw.KEY_R: - reload_shader() - elif key == glfw.KEY_B: - set_filter_mode('BILINEAR') - print("Filter: BILINEAR") - elif key == glfw.KEY_P: - set_filter_mode('POINT') - print("Filter: POINT") - elif key == glfw.KEY_T: - set_filter_mode('TRILINEAR') - print("Filter: TRILINEAR") - # Toggle const0 components (keys 1-4) - elif key == glfw.KEY_1: - g_state['const0'][0] = 1.0 - g_state['const0'][0] - print(f"const0: {g_state['const0']}") - g_state['debug_dirty'] = True - elif key == glfw.KEY_2: - g_state['const0'][1] = 1.0 - g_state['const0'][1] - print(f"const0: {g_state['const0']}") - g_state['debug_dirty'] = True - elif key == glfw.KEY_3: - g_state['const0'][2] = 1.0 - g_state['const0'][2] - print(f"const0: {g_state['const0']}") - g_state['debug_dirty'] = True - elif key == glfw.KEY_4: - g_state['const0'][3] = 1.0 - g_state['const0'][3] - print(f"const0: {g_state['const0']}") - g_state['debug_dirty'] = True - # Toggle const1 components (keys 5-8) - elif key == glfw.KEY_5: - g_state['const1'][0] = 1.0 - g_state['const1'][0] - print(f"const1: {g_state['const1']}") - g_state['debug_dirty'] = True - elif key == glfw.KEY_6: - g_state['const1'][1] = 1.0 - g_state['const1'][1] - print(f"const1: {g_state['const1']}") - g_state['debug_dirty'] = True - elif key == glfw.KEY_7: - g_state['const1'][2] = 1.0 - g_state['const1'][2] - print(f"const1: {g_state['const1']}") - g_state['debug_dirty'] = True - elif key == glfw.KEY_8: - g_state['const1'][3] = 1.0 - g_state['const1'][3] - print(f"const1: {g_state['const1']}") - g_state['debug_dirty'] = True - elif key == glfw.KEY_C: - g_state['mode'] = 'CUBE' if g_state['mode'] == 'QUAD' else 'QUAD' - print(f"Mode: {g_state['mode']}") - g_state['debug_dirty'] = True - elif key == glfw.KEY_SPACE: - g_state['x'] = INIT_X - g_state['y'] = INIT_Y - g_state['z'] = INIT_Z - g_state['yaw'] = INIT_YAW - g_state['pitch'] = INIT_PITCH - g_state['const0'] = INIT_CONST0.copy() - g_state['const1'] = INIT_CONST1.copy() - g_state['debug_dirty'] = True - print("Reset to initial state") - - -def process_held_keys(window, dt): - """Process continuously held keys.""" - moved = False - - if glfw.get_key(window, glfw.KEY_W) == glfw.PRESS: - g_state['z'] += Z_SPEED * dt - moved = True - - if glfw.get_key(window, glfw.KEY_S) == glfw.PRESS: - g_state['z'] -= Z_SPEED * dt - moved = True - if glfw.get_key(window, glfw.KEY_LEFT) == glfw.PRESS: - g_state['x'] += XY_SPEED * dt - moved = True - if glfw.get_key(window, glfw.KEY_RIGHT) == glfw.PRESS: - g_state['x'] -= XY_SPEED * dt - moved = True - if glfw.get_key(window, glfw.KEY_UP) == glfw.PRESS: - g_state['y'] += XY_SPEED * dt - moved = True - if glfw.get_key(window, glfw.KEY_DOWN) == glfw.PRESS: - g_state['y'] -= XY_SPEED * dt - moved = True - - # Rotation (A/D for yaw, Q/E for pitch) - if glfw.get_key(window, glfw.KEY_A) == glfw.PRESS: - g_state['yaw'] += ROT_SPEED * dt - moved = True - if glfw.get_key(window, glfw.KEY_D) == glfw.PRESS: - g_state['yaw'] -= ROT_SPEED * dt - moved = True - if glfw.get_key(window, glfw.KEY_Q) == glfw.PRESS: - g_state['pitch'] += ROT_SPEED * dt - moved = True - if glfw.get_key(window, glfw.KEY_E) == glfw.PRESS: - g_state['pitch'] -= ROT_SPEED * dt - moved = True - - # Clamp Z - g_state['z'] = max(Z_MAX, min(Z_MIN, g_state['z'])) - - if moved: - g_state['debug_dirty'] = True - - -# ----------------------------------------------------------------------------- -# Main -# ----------------------------------------------------------------------------- -def main(): - global BLOCK_WIDTH, BLOCK_HEIGHT - if len(sys.argv) < 5: - print(__doc__) - print("ERROR: Need shader, block_w, block_h, and at least one mipmap PNG") - print("Example: python testbed.py shader.glsl 8 8 mip0.png mip1.png") - sys.exit(1) - - shader_path = sys.argv[1] - try: - BLOCK_WIDTH = int(sys.argv[2]) - BLOCK_HEIGHT = int(sys.argv[3]) - except ValueError: - print(f"ERROR: block_w and block_h must be integers, got '{sys.argv[2]}' '{sys.argv[3]}'") - sys.exit(1) - if BLOCK_WIDTH < 1 or BLOCK_HEIGHT < 1: - print(f"ERROR: block size must be positive, got {BLOCK_WIDTH}x{BLOCK_HEIGHT}") - sys.exit(1) - mip_paths = sys.argv[4:] - print(f"Block size: {BLOCK_WIDTH}x{BLOCK_HEIGHT}") - - g_state['shader_path'] = shader_path - - # Init GLFW - if not glfw.init(): - print("ERROR: Failed to initialize GLFW") - sys.exit(1) - - glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3) - glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3) - glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE) - glfw.window_hint(glfw.RESIZABLE, glfw.TRUE) - glfw.window_hint(glfw.FOCUSED, glfw.TRUE) - glfw.window_hint(glfw.FOCUS_ON_SHOW, glfw.TRUE) - - window = glfw.create_window(WINDOW_WIDTH, WINDOW_HEIGHT, "Deblock Shader Testbed", None, None) - if not window: - glfw.terminate() - print("ERROR: Failed to create window") - sys.exit(1) - - glfw.make_context_current(window) - glfw.set_key_callback(window, key_callback) - glfw.set_framebuffer_size_callback(window, framebuffer_size_callback) - glfw.swap_interval(1) # VSync - glfw.focus_window(window) - - print(f"OpenGL: {glGetString(GL_VERSION).decode()}") - - # Load shader (exit on failure at startup) - g_state['program'] = load_shader(shader_path) - if g_state['program'] is None: - glfw.terminate() - sys.exit(1) - - # Load texture - g_state['texture'], g_state['tex_size'] = load_mipmap_texture(mip_paths) - set_filter_mode('BILINEAR') - - # Create quad - aspect = g_state['tex_size'][0] / g_state['tex_size'][1] - g_state['quad_vao'] = create_quad(aspect) - - # Create cube - g_state['cube_vao'], g_state['cube_index_count'] = create_cube(1.0) - - # Init debug rendering - init_debug_rendering() - - glEnable(GL_DEPTH_TEST) - glClearColor(0.2, 0.2, 0.2, 1.0) - - g_state['last_time'] = glfw.get_time() - - # Main loop - while not glfw.window_should_close(window): - # Delta time - now = glfw.get_time() - dt = now - g_state['last_time'] - g_state['last_time'] = now - - # Input - glfw.poll_events() - process_held_keys(window, dt) - - # Projection matrix (recalculate for resize) - proj = perspective_matrix(FOV_DEGREES, WINDOW_WIDTH / WINDOW_HEIGHT, 0.001, 100.0) - - # Clear - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) - - # Draw quad or cube - glUseProgram(g_state['program']) - - # MVP (include rotation for cube mode) - trans = translation_matrix(g_state['x'], g_state['y'], g_state['z']) - rot_y = rotation_matrix_y(g_state['yaw']) - rot_x = rotation_matrix_x(g_state['pitch']) - model = trans @ rot_y @ rot_x - mvp = proj @ model - - loc = glGetUniformLocation(g_state['program'], "mvp") - if loc >= 0: - glUniformMatrix4fv(loc, 1, GL_TRUE, mvp) - - loc = glGetUniformLocation(g_state['program'], "tex") - if loc >= 0: - glUniform1i(loc, 0) - - loc = glGetUniformLocation(g_state['program'], "texSize") - if loc >= 0: - glUniform4f(loc, float(g_state['tex_size'][0]), float(g_state['tex_size'][1]), BLOCK_WIDTH, BLOCK_HEIGHT); - - loc = glGetUniformLocation(g_state['program'], "const0") - if loc >= 0: - c = g_state['const0'] - glUniform4f(loc, c[0], c[1], c[2], c[3]) - - loc = glGetUniformLocation(g_state['program'], "const1") - if loc >= 0: - c = g_state['const1'] - glUniform4f(loc, c[0], c[1], c[2], c[3]) - - glActiveTexture(GL_TEXTURE0) - glBindTexture(GL_TEXTURE_2D, g_state['texture']) - - if g_state['mode'] == 'CUBE': - glBindVertexArray(g_state['cube_vao']) - glDrawElements(GL_TRIANGLES, g_state['cube_index_count'], GL_UNSIGNED_INT, None) - else: - glBindVertexArray(g_state['quad_vao']) - glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, None) - - # Draw debug overlay - draw_debug_text() - - glfw.swap_buffers(window) - - glfw.terminate() - print("Done.") - - -if __name__ == "__main__": - main()