r/opengl

▲ 13 r/opengl

[HELP] Spotlight doesn't work

version 330 core

out vec4 FragColor; in vec2 TexCoo; in vec3 FragPos; in vec3 Normal;

struct DirLight { vec3 direction; vec3 ambient; vec3 diffuse; vec3 specular; }; struct PointLight { vec3 position; vec3 ambient; vec3 diffuse; vec3 specular;

float constant;
float linear;
float quadratic;

};

struct Materijal { sampler2D diffuse; sampler2D specular; float shininess; };

struct SpotLight { vec3 position; float cutOff; float outcutOff; vec3 direction;

float constant;
float linear;
float quadratic;

vec3 ambient;
vec3 diffuse;
vec3 specular;

};

#define NR_POINT_LIGHTS 4 uniform SpotLight spotlight; uniform PointLight pointLights[NR_POINT_LIGHTS]; uniform DirLight dirLight; uniform Materijal materijal; uniform vec3 viewPos; vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 viewDir); vec3 CalcDirLight(DirLight light, vec3 normal, vec3 viewDir); vec3 CalcPointLight(PointLight light, vec3 normal, vec3 viewDir); void main() { vec3 norm = normalize(Normal); vec3 viewDir = normalize(viewPos - FragPos); vec3 result = CalcDirLight(dirLight, norm, viewDir); for(int i =0;i<NR_POINT_LIGHTS;i++) result += CalcPointLight(pointLights[i], norm, viewDir); result += CalcSpotLight(spotlight,norm,viewDir); FragColor = vec4(result,1.0); } vec3 CalcDirLight(DirLight light, vec3 normal,vec3 viewDir) { vec3 lightDir = normalize(-light.direction); float diff = max(dot(lightDir, normal),0.0); vec3 reflectDir= reflect(-lightDir, normal); float spec = pow(max(0.0, dot(reflectDir, viewDir)), materijal.shininess); vec3 ambient = light.ambient * vec3(texture(materijal.diffuse, TexCoo)); vec3 diffuse = light.diffuse * diff * vec3(texture(materijal.diffuse, TexCoo)); vec3 specular = light.specular * spec * vec3(texture(materijal.specular,TexCoo)); return (ambient + specular + diffuse); } vec3 CalcPointLight(PointLight light, vec3 normal, vec3 viewDir) { vec3 lightDir = normalize(light.position - FragPos); float diff = max(dot(lightDir, normal),0.0); vec3 reflectDir = reflect(-lightDir, normal); float spec = pow(max(dot(viewDir, reflectDir),0.0),materijal.shininess); float distance = length(light.position - FragPos); float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * distance * distance); vec3 ambient = light.ambient * vec3(texture(materijal.diffuse, TexCoo)); vec3 diffuse = light.diffuse * diff * vec3(texture(materijal.diffuse, TexCoo)); vec3 specular = light.specular * spec * vec3(texture(materijal.specular,TexCoo)); ambient*= attenuation; diffuse *= attenuation; specular = attenuation; return (ambient + diffuse + specular); } vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 viewDir) { vec3 lightDir = normalize(light.position - FragPos); float diff = max(dot(normal,lightDir),0.0); vec3 reflectDir = reflect(-lightDir, normal); float spec = pow(max(dot(reflectDir, viewDir),0.0),materijal.shininess); float distance = length(light.position - FragPos); float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * distance * distance); float theta = dot(normalize(-light.direction), lightDir); float epsilon = light.cutOff - light.outcutOff; float intensity = clamp((theta - light.outcutOff) / epsilon, 0.0, 1.0); vec3 ambient = light.ambient * vec3(texture(materijal.diffuse, TexCoo)); vec3 diffuse = light.diffuse * diff * vec3(texture(materijal.diffuse, TexCoo)); vec3 specular = light.specular * spec * vec3(texture(materijal.specular,TexCoo)); ambient= attenuation * intensity; diffuse *= attenuation * intensity; specular *= attenuation * intensity; return (ambient + diffuse + specular);

}

this is the main function inside the while (!glfwWindowShouldClose(window)) among other standard stuff /* Here we set all the uniforms for the 5/6 types of lights we have. We have to set them manually and index the proper PointLight struct in the array to set each uniform variable. This can be done more code-friendly by defining light types as classes and set their values in there, or by using a more efficient uniform approach by using 'Uniform buffer objects', but that is something we'll discuss in the 'Advanced GLSL' tutorial. */ // directional light lightingShader.setVec3("dirLight.direction", -0.2f, -1.0f, -0.3f); lightingShader.setVec3("dirLight.ambient", 0.05f, 0.05f, 0.05f); lightingShader.setVec3("dirLight.diffuse", 0.4f, 0.4f, 0.4f); lightingShader.setVec3("dirLight.specular", 0.5f, 0.5f, 0.5f); // point light 1 lightingShader.setVec3("pointLights[0].position", pointLightPositions[0]); lightingShader.setVec3("pointLights[0].ambient", 0.05f, 0.05f, 0.05f); lightingShader.setVec3("pointLights[0].diffuse", 0.8f, 0.8f, 0.8f); lightingShader.setVec3("pointLights[0].specular", 1.0f, 1.0f, 1.0f); lightingShader.setFloat("pointLights[0].constant", 1.0f); lightingShader.setFloat("pointLights[0].linear", 0.09); lightingShader.setFloat("pointLights[0].quadratic", 0.032); // point light 2 lightingShader.setVec3("pointLights[1].position", pointLightPositions[1]); lightingShader.setVec3("pointLights[1].ambient", 0.05f, 0.05f, 0.05f); lightingShader.setVec3("pointLights[1].diffuse", 0.8f, 0.8f, 0.8f); lightingShader.setVec3("pointLights[1].specular", 1.0f, 1.0f, 1.0f); lightingShader.setFloat("pointLights[1].constant", 1.0f); lightingShader.setFloat("pointLights[1].linear", 0.09); lightingShader.setFloat("pointLights[1].quadratic", 0.032); // point light 3 lightingShader.setVec3("pointLights[2].position", pointLightPositions[2]); lightingShader.setVec3("pointLights[2].ambient", 0.05f, 0.05f, 0.05f); lightingShader.setVec3("pointLights[2].diffuse", 0.8f, 0.8f, 0.8f); lightingShader.setVec3("pointLights[2].specular", 1.0f, 1.0f, 1.0f); lightingShader.setFloat("pointLights[2].constant", 1.0f); lightingShader.setFloat("pointLights[2].linear", 0.09); lightingShader.setFloat("pointLights[2].quadratic", 0.032); // point light 4 lightingShader.setVec3("pointLights[3].position", pointLightPositions[3]); lightingShader.setVec3("pointLights[3].ambient", 0.05f, 0.05f, 0.05f); lightingShader.setVec3("pointLights[3].diffuse", 0.8f, 0.8f, 0.8f); lightingShader.setVec3("pointLights[3].specular", 1.0f, 1.0f, 1.0f); lightingShader.setFloat("pointLights[3].constant", 1.0f); lightingShader.setFloat("pointLights[3].linear", 0.09); lightingShader.setFloat("pointLights[3].quadratic", 0.032); // spotLight lightingShader.setVec3("spotLight.position", camera.Position); lightingShader.setVec3("spotLight.direction", camera.Front); lightingShader.setVec3("spotLight.ambient", 0.0f, 0.0f, 0.0f); lightingShader.setVec3("spotLight.diffuse", 1.0f, 1.0f, 1.0f); lightingShader.setVec3("spotLight.specular", 1.0f, 1.0f, 1.0f); lightingShader.setFloat("spotLight.constant", 1.0f); lightingShader.setFloat("spotLight.linear", 0.09); lightingShader.setFloat("spotLight.quadratic", 0.032); lightingShader.setFloat("spotLight.cutOff", glm::cos(glm::radians(12.5f))); lightingShader.setFloat("spotLight.outerCutOff", glm::cos(glm::radians(15.0f)));

    // view/projection transformations
    glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
    glm::mat4 view = camera.GetViewMatrix();
    lightingShader.setMat4("projection", projection);
    lightingShader.setMat4("view", view);

    // world transformation
    glm::mat4 model = glm::mat4(1.0f);
    lightingShader.setMat4("model", model);

    // bind diffuse map
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, diffuseMap);
    // bind specular map
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, specularMap);

    // render containers
    glBindVertexArray(cubeVAO);
    for (unsigned int i = 0; i &lt; 10; i++)
    {
        // calculate the model matrix for each object and pass it to shader before drawing
        glm::mat4 model = glm::mat4(1.0f);
        model = glm::translate(model, cubePositions[i]);
        float angle = 20.0f * i;
        model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
        lightingShader.setMat4("model", model);

        glDrawArrays(GL_TRIANGLES, 0, 36);
    }

     // also draw the lamp object(s)
     lightCubeShader.use();
     lightCubeShader.setMat4("projection", projection);
     lightCubeShader.setMat4("view", view);

     // we now draw as many light bulbs as we have point lights.
     glBindVertexArray(lightCubeVAO);
     for (unsigned int i = 0; i &lt; 4; i++)
     {
         model = glm::mat4(1.0f);
         model = glm::translate(model, pointLightPositions[i]);
         model = glm::scale(model, glm::vec3(0.2f)); // Make it a smaller cube
         lightCubeShader.setMat4("model", model);
         glDrawArrays(GL_TRIANGLES, 0, 36);
     }
u/DejanStojanovic1 — 22 hours ago
▲ 54 r/opengl+1 crossposts

Marching Tetrahedra - Volumetric Render Engine (OpenGL/C++) (Opensource)

We added Marching Tetrahedra Rendering effect to our Volumetric Render Engine.

Here, we Render our Volume data as a set of Polygon meshes by extracting 'iso surface'. It goes through whole dataset and tries to fit a polygon based on data values to calculate a polygonal mesh from the volume dataset.

Here's the Git Repo Link - https://github.com/mikejernil/volumetric-render-engine

We are building this over at 3D ENGINERD. & are planning to push our implement more features starting with Custom file loading, and support for more volumetric formats like DICOM, VDB etc.

u/Neither_Coffee_2308 — 2 days ago
▲ 8 r/opengl+2 crossposts

AO46: RGB32 Buffer Views + GL 4.3 SSBO Atomic Groundwork

  • RGB32 buffer views now support real FLOAT, UINT, and SINT variants through live sampler state, with the unsigned path retaining hardware draw/readback coverage.
  • GL 4.3 groundwork now includes static-index SSBO atomics lowering through Mesa and executing a fenced 32-thread atomic-add verification.
reddit.com
u/Adventurous_Chef2225 — 4 days ago
▲ 3 r/opengl

need help with drawing text

i've been trying to tackle this problem for a while now and luckily it works, well ... kinda. I see the text but the glyphs are always slightly misplaced, i have a feeling it's floating-point inaccuracies but i have no idea how to fix it, here are the snippets:

```c

void drawText(WS_Shell* shell, GlyphCacheDA* cache, char* text, FT_Face font, float x, float y) {

float pen_x = x;

for (char* c = text; *c != '\0'; c++) {

uint32_t codepoint = next_utf8(&text);

bool is_drawn = false;

if (codepoint == ' ') {pen_x += cache->items[0].advance; continue;} // if space, just advance

// search in cache first

for (int i = 0; i<cache->count; i++) {

if (cache->items[i].codepoint == codepoint) {

drawTexturedRectangle(pen_x, y, cache->items[i].width, cache->items[i].height, cache->items[i].texture);

pen_x += cache->items[i].advance;

is_drawn = true;

break;

}

}

if (is_drawn) continue;

FT_Load_Glyph(font, FT_Get_Char_Index(font, codepoint), FT_LOAD_RENDER);

FT_Render_Glyph(font->glyph, FT_RENDER_MODE_NORMAL);

GLuint glyph_texture;

glGenTextures(1, &glyph_texture);

glBindTexture(GL_TEXTURE_2D, glyph_texture);

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, font->glyph->bitmap.width, font->glyph->bitmap.rows, 0, GL_RED, GL_UNSIGNED_BYTE, font->glyph->bitmap.buffer);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

Glyph glyph = {

.codepoint = codepoint,

.texture = glyph_texture,

.width = ((float)font->glyph->bitmap.width/shell->settings->width)*2,

.height = ((float)font->glyph->bitmap.rows/shell->settings->height)*2,

};

// TODO: add support for non-monospace fonts

glyph.advance = glyph.width;

nob_da_append(cache, glyph);

drawTexturedRectangle(pen_x, y, glyph.width, glyph.height, glyph.texture);

pen_x += glyph.advance;

is_drawn = false;

}

}

```
draw rectangle:

```c

void drawTexturedRectangle(float x, float y, float w, float h, GLuint texture) {

// Vertex data for the rectangle

float vertices[] = {

x, y, 0.0f, 1.0f,

x + w, y, 1.0f, 1.0f,

x, y + h, 0.0f, 0.0f,

x + w, y + h, 1.0f, 0.0f

};

// Indices for the rectangle

unsigned int indices[] = {

0, 1, 2,

1, 3, 2

};

// VBO and VAO

GLuint VBO, VAO, EBO;

glGenBuffers(1, &VBO);

glGenBuffers(1, &EBO);

glGenVertexArrays(1, &VAO);

glBindVertexArray(VAO);

glBindBuffer(GL_ARRAY_BUFFER, VBO);

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);

glEnableVertexAttribArray(0);

glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));

glEnableVertexAttribArray(1);

glBindBuffer(GL_ARRAY_BUFFER, 0);

// Draw the rectangle

glBindTexture(GL_TEXTURE_2D, texture);

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

// Clean up

glDeleteBuffers(1, &VBO);

glDeleteBuffers(1, &EBO);

glDeleteBuffers(1, &VAO);

}

```
the dinamic array for cache i stolen from nob.h, each glyph's cache looks like this:

```c
typedef struct {

uint32_t codepoint;

GLuint texture;

float width;

float height;

float advance;

} Glyph;

```

everything else is pretty standard opengl stuff, thanks for helping

reddit.com
u/Senior-Question693 — 4 days ago
▲ 22 r/opengl+2 crossposts

Built a game engine

I've been working on my own game engine from scratch for some years using C and OpenGL and I finally made a video showing the process.

The engine handles things like rendering, assets, shaders, lighting, and more, and I also used it to make a game, a test game though, it still needs a lot of polish.

Watch video here: https://youtu.be/LRC4iJWASYU?si=_3ua8rcwesyaQtO9

If you're interested in graphics programming, game engines, I'd love to hear your thoughts and feedback.

u/_Bethel__ — 5 days ago
▲ 13 r/opengl

Hello, I am new to opengl. Can someone explain compute shaders to me

so i recently started playing around with opengl and wanted to try and make a raytracer with compute shaders, but there isn't much info about them. i looked at the tutorial on learnopengl but that wasn't very helpful. can someone clue me in?

reddit.com
u/yuchi902 — 5 days ago
▲ 0 r/opengl

Well.... We did meet a hard boundary finally , beyond which continuing would be risky for systems

After a few weeks of pushing AO46 much further than I originally expected, I think I’ve finally reached the point where continuing the same reverse-engineering path would cross from graphics-driver research into territory I’m not comfortable touching on real machines.

For context, AO46 started as an attempt to replace Apple’s deprecated OpenGL stack with a modern OpenGL 4.6 implementation on macOS.

The project has already moved through several architecture stages:

  • replacing the old OpenGL.framework-facing stack,
  • Mesa/Gallium integration,
  • NIR,
  • Asahi’s AGX compiler/backend work,
  • macOS-specific resource management,
  • GPU queue/submission tracing,
  • and finally following the path between Apple-generated GPU code and the actual executable GPU mapping used by the kernel driver.

For quite a while I assumed the remaining problem was simply:

“figure out how macOS submits the same AGX command buffers that Asahi submits on Linux.”

It turns out that was too simple.

What the reverse pass has increasingly shown is that Apple does not treat executable GPU code as just another buffer with a magic flag.

There is a fairly clear trust boundary.

Very roughly, the path looks like:

Apple GPU compiler output
        ↓
Apple-owned code resource
        ↓
private relocation / preparation step
        ↓
restricted executable GPU mapping
        ↓
queue consumption

Generic buffers do not appear to just become executable after the fact.

And the important bit is that the transition into that executable mapping is not exposed like a normal public allocation API.

At this point, the remaining work would mean investigating the enforcement side of that boundary rather than merely understanding the graphics ABI around it.

That is where I’m stopping.

Not because the project suddenly became impossible, but because there is a difference between:

reverse engineering an undocumented graphics driver

and

deliberately trying to defeat a platform security boundary in order to make arbitrary GPU memory executable.

The latter is not something I want AO46 to become.

And honestly, that boundary existing is probably a good thing.

So is AO46 dead?

No.

Not even remotely.

A huge amount of useful architecture now exists that did not exist a few weeks ago.

The project now has concrete implementations/documentation for:

  • OpenGL.framework replacement
  • CGL/NSOpenGL compatibility
  • Mesa Gallium integration
  • NIR shader pipeline
  • AGX compiler integration
  • macOS BO/resource handling
  • synchronization/fence ownership
  • queue tracing
  • Apple GPU submission structure analysis
  • Apple compiler-object parsing
  • executable-code provenance tracking

The original project was basically:

OpenGL
 ↓
Mesa
 ↓
Metal

The current research has gone much deeper:

OpenGL
 ↓
Mesa
 ↓
Gallium
 ↓
NIR
 ↓
AGX backend
 ↓
macOS GPU infrastructure

That is still extremely valuable.

What we do not currently have is a legitimate way to complete the final transition:

Mesa-generated AGX code
        ↓
???
        ↓
Apple-authorized executable GPU mapping

And fabricating or bypassing that transition is exactly the point where I’m drawing the line.

Interestingly, this also answers one of the biggest questions people kept asking

A lot of people assumed the blocker would be:

  • AGX ISA differences,
  • WindowServer,
  • Metal interoperability,
  • command buffer encoding,
  • or simply “Apple doesn’t expose IOKit.”

Those are all problems.

But they were not the final one.

The deepest blocker is much more architectural:

Apple owns the transition that turns compiled GPU code into something the GPU is actually allowed to execute.

That is a considerably stronger boundary than I expected when this project started.

What happens next?

Probably one of three directions.

1. Stay above the protected execution boundary

Use Apple-supported APIs for the final executable-code handoff while keeping as much of Mesa/AGX/OpenGL outside that boundary as possible.

This is currently the most realistic direction.

2. Continue documenting the architecture

There is still a huge amount of useful work that can be done without trying to bypass anything.

For example:

  • command submission structures
  • resource lifetime rules
  • synchronization semantics
  • shader metadata
  • queue behavior
  • compiler object formats
  • AGX generation differences

All of that is legitimate reverse-engineering work and could be useful far beyond AO46.

3. Wait for a better supported interface

Apple may eventually expose more GPU infrastructure through DriverKit, Metal evolution, or some future API.

If a legitimate executable-code path appears, AO46 can plug into it.

The upper 90% of the architecture would not need to be thrown away.

Honestly, I’m pretty happy we found this

This might sound weird, but discovering a hard architectural boundary is actually a useful result.

A month ago the unanswered question was:

“Can Mesa/Asahi actually talk to Apple’s GPU stack on macOS?”

Now the question is much narrower:

“How can externally generated AGX code legitimately enter Apple’s trusted executable-code pipeline?”

That is a much better-defined problem.

And importantly, we now know where not to push.

So for now the project is stepping back from the protected execution path and focusing on everything around it.

AO46 is still alive.

The research path just finally reached a sign that says:

            ┌─────────────────────────────┐
            │   HERE BE SECURITY POLICY   │
            │                             │
            │ graphics engineers pls stop │
            └─────────────────────────────┘

Which, considering how absurdly deep this project has gone in three weeks, was probably inevitable.

reddit.com
u/Adventurous_Chef2225 — 7 days ago
▲ 43 r/opengl+1 crossposts

VertexArt - this is how it started (Intel N4100 CPU / Free Pascal 3.2.2 / GLFW3 / OpenGL 3.3 / data-oriented / only 16 byte vertex, nothing else)

u/AcanthaceaeNew774 — 7 days ago
▲ 2 r/opengl

Can't get the project to load textures

Hello,

This is my project, perfectly up to date: opengl

When i launch it, it throws this error log: unit 0 GLD_TEXTURE_INDEX_2D is unloadable and bound to sampler type (Float) - using zero texture because texture unloadable, and doesn't load my model

To note it worked before adding the move constructor to the shader.h and mesh.h, but reverting them doesn't work

I've tried everything, scouted forums, asked AI (reluctantly), but nothing worked, so I'm asking here for help from people smarter then me

Thanks for the time.

u/DaviPlay — 5 days ago
▲ 138 r/opengl+1 crossposts

3D Volumetric Render Engine (OpenGL &amp; C++) (Colormap)

Hey Everyone - we at 3D ENGINERD. are building a Volumetric Render Engine for Windows(Native). It's being built with OpenGL & C++

We're planning to publish the code open-source under MIT License on our Github (https://github.com/mikejernil) tomorrow, so you all can try it out and use it for your own applications. ✨

Currently it has -

  1. Volumetric RAW visualization support
  2. Different types of rendering (Colormap, Iso-surface etc.)
  3. Rotate & Zoom Controls (for easy navigation)
  4. 6 slicing planes to visualization cross-sections

We are planning to build more features and add support for more volumetric formats (like DICOM, VDB etc) soon!

Colormap Classification of an Internal Combustion Engine(shown in video):

Here we can see the volume data with colour values mapped to its material density.

As per our current Colormap we can see the Red is Higher density whereas blue is Low density Noise.

Applications : 

  1. Medical imaging
  2. Industrial testing
  3. Scientific visualization of data

It's till very early-stages and we're actively exploring into Volumetric rendering at the moment, any constructive feedback would be appreciated, thanks! :)

u/Neither_Coffee_2308 — 9 days ago
▲ 212 r/opengl+5 crossposts

Sunlit Kingdom, Free Demo on Steam!

Just released the demo for Sunlit Kingdom, a 3D tower defense where you place and control three hero types instead of static towers.

Each hero is highly customisable & unique, different abilities, etc. Game's built in a custom engine.

Demo's free on Steam: https://store.steampowered.com/app/5011470/Sunlit_Kingdom/

I hope u guys enjoy it! Any feedback is welcome.

u/OfMagicAndWars — 13 days ago
▲ 73 r/opengl+3 crossposts

SSAO optimization

Hi folks,
I realized that the SSAO implementation from LearnOpenGL had become a real bottleneck in my renderer.

I was getting around 45 FPS, so I profiled the pass and made a few changes:
● Reduced the SSAO framebuffer resolution
● Got rid of position/depth reconstruction
● Reduced the kernel size

After the changes, I’m getting around 77 FPS with very little noticeable visual difference.
45 FPS → 77 FPS just by making the SSAO pass do less work.

It’s a good reminder that tutorial implementations are great for learning, but once you’re building an actual renderer, you eventually have to question every piece of work you’re asking the GPU to do.

Github: https://github.com/xms0g/abra

u/Background_Shift5408 — 12 days ago