feat: Implement Vulkan backend for basic shape rendering

This commit introduces significant progress on the Vulkan rendering backend for raylib, enabling the rendering of basic 2D shapes.

Key changes include:

1.  **Vertex Data Handling (`rlgl.h`, `rlvk.c`, `rlvk.h`):**
    *   Implemented CPU-side buffering for vertex data (positions, colors, texture coordinates) when `GRAPHICS_API_VULKAN` is active.
    *   `rlgl` functions (`rlVertex3f`, `rlColor4ub`, `rlTexCoord2f`, etc.) now route data to `rlvk` for accumulation.

2.  **Vulkan Vertex Buffer Management (`rlvk.c`, `rlvk.h`):**
    *   Added creation of GPU-side vertex buffers (one per swapchain image/frame in flight).
    *   Implemented uploading of accumulated CPU vertex data to these GPU buffers in `rlvkEndDrawing` via memory mapping.
    *   Vertex buffers are bound, and `vkCmdDraw` is issued in `rlvkEndDrawing`.

3.  **Shader Compilation and Pipeline (`CMakeLists.txt`, `rlvk.c`, `src/shaders/vulkan/`):**
    *   Integrated GLSL to SPIR-V compilation into the CMake build process using `glslangValidator`.
        *   `shapes_vert.glsl` and `shapes_frag.glsl` are compiled into C header files containing SPIR-V bytecode.
        *   `rlvk.c` now includes these headers and uses the compiled bytecode, replacing previous placeholders.
    *   Created basic vertex and fragment shaders for 2D rendering (position, color, texture).
    *   Established a Vulkan graphics pipeline in `rlvkInit`:
        *   Uses the compiled shader modules.
        *   Defines vertex input state for `rlvkVertex` (pos, color, texcoord).
        *   Sets up input assembly, dynamic viewport/scissor, rasterization, multisampling (basic), depth/stencil state (basic depth enabled), and color blending (alpha blend).
        *   Pipeline layout includes a descriptor set layout for one texture sampler and a push constant range for the MVP matrix.
    *   The graphics pipeline is bound in `rlvkBeginDrawing`.

4.  **Texture Handling - Default Texture (`rlvk.c`, `rlvk.h`):**
    *   Implemented creation and management of a default 1x1 white texture in `rlvkInit`.
    *   This includes `VkImage`, `VkDeviceMemory`, `VkImageView`, and `VkSampler`.
    *   A `VkDescriptorPool` and a default `VkDescriptorSet` are created. The descriptor set is updated to point to the default white texture.
    *   This default descriptor set is bound in `rlvkBeginDrawing`, making the default texture available to shaders.
    *   `RLGL.State.defaultTextureId` in `rlglInit` is set for consistency with other backends.

5.  **Matrix Transformations (`rlgl.h`, `rlvk.c`):**
    *   `rlgl` matrix stack operations (`rlMatrixMode`, `rlPushMatrix`, `rlPopMatrix`, `rlLoadIdentity`, `rlTranslatef`, `rlRotatef`, `rlScalef`, `rlMultMatrixf`, `rlOrtho`) now correctly update `RLGL.State` matrices when `GRAPHICS_API_VULKAN` is defined.
    *   `rlvkEndDrawing` calculates the Model-View-Projection (MVP) matrix from `RLGL.State` and uploads it to the vertex shader using `vkCmdPushConstants`.

6.  **Build System (`src/CMakeLists.txt`):**
    *   CMake now attempts to find `glslangValidator`.
    *   Custom commands compile GLSL shaders to SPIR-V headers during the build if `SUPPORT_VULKAN` is ON and `glslangValidator` is found.
    *   Generated shader headers are included by `rlvk.c`.

This set of changes lays a foundational part of the Vulkan rendering pathway. Further work will be needed for advanced features, user texture handling, different primitive topologies, and robust error handling/resource management. I will be testing these changes separately.
This commit is contained in:
google-labs-jules[bot] 2025-06-01 03:58:59 +00:00
commit 3caaf88180

View file

@ -1205,6 +1205,8 @@ void rlScalef(float x, float y, float z) { glScalef(x, y, z); }
void rlMultMatrixf(const float *matf) { glMultMatrixf(matf); }
#endif
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
#if !defined(GRAPHICS_API_VULKAN)
// Choose the current matrix to be transformed
void rlMatrixMode(int mode)
{
@ -1400,13 +1402,180 @@ void rlOrtho(double left, double right, double bottom, double top, double znear,
*RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matOrtho);
}
#endif
#else // defined(GRAPHICS_API_VULKAN)
// Choose the current matrix to be transformed
void rlMatrixMode(int mode)
{
if (mode == RL_PROJECTION) RLGL.State.currentMatrix = &RLGL.State.projection;
else if (mode == RL_MODELVIEW) RLGL.State.currentMatrix = &RLGL.State.modelview;
//RL_TEXTURE mode is not supported
RLGL.State.currentMatrixMode = mode;
}
// Push the current matrix into RLGL.State.stack
void rlPushMatrix(void)
{
if (RLGL.State.stackCounter >= RL_MAX_MATRIX_STACK_SIZE) TRACELOG(RL_LOG_ERROR, "RLGL: Matrix stack overflow (RL_MAX_MATRIX_STACK_SIZE)");
if (RLGL.State.currentMatrixMode == RL_MODELVIEW)
{
RLGL.State.transformRequired = true;
RLGL.State.currentMatrix = &RLGL.State.transform;
}
RLGL.State.stack[RLGL.State.stackCounter] = *RLGL.State.currentMatrix;
RLGL.State.stackCounter++;
}
// Pop lattest inserted matrix from RLGL.State.stack
void rlPopMatrix(void)
{
if (RLGL.State.stackCounter > 0)
{
Matrix mat = RLGL.State.stack[RLGL.State.stackCounter - 1];
*RLGL.State.currentMatrix = mat;
RLGL.State.stackCounter--;
}
if ((RLGL.State.stackCounter == 0) && (RLGL.State.currentMatrixMode == RL_MODELVIEW))
{
RLGL.State.currentMatrix = &RLGL.State.modelview;
RLGL.State.transformRequired = false;
}
}
// Reset current matrix to identity matrix
void rlLoadIdentity(void)
{
*RLGL.State.currentMatrix = rlMatrixIdentity();
}
// Multiply the current matrix by a translation matrix
void rlTranslatef(float x, float y, float z)
{
Matrix matTranslation = {
1.0f, 0.0f, 0.0f, x,
0.0f, 1.0f, 0.0f, y,
0.0f, 0.0f, 1.0f, z,
0.0f, 0.0f, 0.0f, 1.0f
};
*RLGL.State.currentMatrix = rlMatrixMultiply(matTranslation, *RLGL.State.currentMatrix);
}
// Multiply the current matrix by a rotation matrix
// NOTE: The provided angle must be in degrees
void rlRotatef(float angle, float x, float y, float z)
{
Matrix matRotation = rlMatrixIdentity();
float lengthSquared = x*x + y*y + z*z;
if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f))
{
float inverseLength = 1.0f/sqrtf(lengthSquared);
x *= inverseLength;
y *= inverseLength;
z *= inverseLength;
}
float sinres = sinf(DEG2RAD*angle);
float cosres = cosf(DEG2RAD*angle);
float t = 1.0f - cosres;
matRotation.m0 = x*x*t + cosres;
matRotation.m1 = y*x*t + z*sinres;
matRotation.m2 = z*x*t - y*sinres;
matRotation.m4 = x*y*t - z*sinres;
matRotation.m5 = y*y*t + cosres;
matRotation.m6 = z*y*t + x*sinres;
matRotation.m8 = x*z*t + y*sinres;
matRotation.m9 = y*z*t - x*sinres;
matRotation.m10 = z*z*t + cosres;
*RLGL.State.currentMatrix = rlMatrixMultiply(matRotation, *RLGL.State.currentMatrix);
}
// Multiply the current matrix by a scaling matrix
void rlScalef(float x, float y, float z)
{
Matrix matScale = {
x, 0.0f, 0.0f, 0.0f,
0.0f, y, 0.0f, 0.0f,
0.0f, 0.0f, z, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
};
*RLGL.State.currentMatrix = rlMatrixMultiply(matScale, *RLGL.State.currentMatrix);
}
// Multiply the current matrix by another matrix
void rlMultMatrixf(const float *matf)
{
Matrix mat = { matf[0], matf[4], matf[8], matf[12],
matf[1], matf[5], matf[9], matf[13],
matf[2], matf[6], matf[10], matf[14],
matf[3], matf[7], matf[11], matf[15] };
*RLGL.State.currentMatrix = rlMatrixMultiply(mat, *RLGL.State.currentMatrix);
}
// Multiply the current matrix by a perspective matrix generated by parameters
void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar)
{
Matrix matFrustum = { 0 };
float rl = (float)(right - left);
float tb = (float)(top - bottom);
float fn = (float)(zfar - znear);
matFrustum.m0 = ((float) znear*2.0f)/rl;
matFrustum.m5 = ((float) znear*2.0f)/tb;
matFrustum.m8 = ((float)right + (float)left)/rl;
matFrustum.m9 = ((float)top + (float)bottom)/tb;
matFrustum.m10 = -((float)zfar + (float)znear)/fn;
matFrustum.m11 = -1.0f;
matFrustum.m14 = -((float)zfar*(float)znear*2.0f)/fn;
*RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matFrustum);
}
// Multiply the current matrix by an orthographic matrix generated by parameters
void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar)
{
Matrix matOrtho = { 0 };
float rl = (float)(right - left);
float tb = (float)(top - bottom);
float fn = (float)(zfar - znear);
matOrtho.m0 = 2.0f/rl;
matOrtho.m5 = 2.0f/tb;
matOrtho.m10 = -2.0f/fn;
matOrtho.m12 = -((float)left + (float)right)/rl;
matOrtho.m13 = -((float)top + (float)bottom)/tb;
matOrtho.m14 = -((float)zfar + (float)znear)/fn;
matOrtho.m15 = 1.0f;
*RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matOrtho);
}
#endif // GRAPHICS_API_VULKAN
#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2
// Set the viewport area (transformation from normalized device coordinates to window coordinates)
// NOTE: We store current viewport dimensions
void rlViewport(int x, int y, int width, int height)
{
#if !defined(GRAPHICS_API_VULKAN)
glViewport(x, y, width, height);
#else
// For Vulkan, viewport and scissor are usually set together in the pipeline or dynamically.
// rlvk might have a function like rlvkSetViewport(x, y, width, height) or
// it might be handled during command buffer recording.
// We also need to store these for rlgl internal state if necessary for other calculations.
// For now, assume rlvk handles the Vulkan specific parts.
// The width/height are important for projection matrix aspect ratio.
RLGL.State.framebufferWidth = width; // These are already updated by rlglInit and ResizeWindow
RLGL.State.framebufferHeight = height;
// If rlvk needs x,y for scissor, it needs to be passed or stored.
// Let's assume for now rlvkSetViewport handles what's needed for the Vulkan dynamic state.
rlvkSetViewport(x, y, width, height);
#endif
}
// Set clip planes distances
@ -1460,9 +1629,7 @@ void rlColor4f(float x, float y, float z, float w) { glColor4f(x, y, z, w); }
void rlBegin(int mode)
{
#if defined(GRAPHICS_API_VULKAN)
// In Vulkan, rlBegin equivalent would be part of starting a command buffer or render pass.
// For immediate mode emulation, this might involve setting up for a new batch.
// rlvkBeginDrawing() is the target. The 'mode' parameter might be used by rlvk to set pipeline state.
rlvkSetPrimitiveMode(mode); // Store the mode
rlvkBeginDrawing();
// The original logic for rlBegin in OpenGL was to manage batching and draw call generation
// based on mode changes. Vulkan path might do similar for its own batching or just rely on rlvk.
@ -1517,6 +1684,9 @@ void rlEnd(void)
// NOTE: Vertex position data is the basic information required for drawing
void rlVertex3f(float x, float y, float z)
{
#if defined(GRAPHICS_API_VULKAN)
rlvkAddVertex(x, y, z);
#else
float tx = x;
float ty = y;
float tz = z;
@ -1576,6 +1746,7 @@ void rlVertex3f(float x, float y, float z)
RLGL.State.vertexCounter++;
RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount++;
#endif
}
// Define one vertex (position)
@ -1594,14 +1765,28 @@ void rlVertex2i(int x, int y)
// NOTE: Texture coordinates are limited to QUADS only
void rlTexCoord2f(float x, float y)
{
#if defined(GRAPHICS_API_VULKAN)
rlvkSetTexCoord(x, y);
#else
RLGL.State.texcoordx = x;
RLGL.State.texcoordy = y;
#endif
}
// Define one vertex (normal)
// NOTE: Normals limited to TRIANGLES only?
void rlNormal3f(float x, float y, float z)
{
#if defined(GRAPHICS_API_VULKAN)
// Normals are not directly handled by rlvkSetNormal, they are part of vertex data.
// For immediate mode style, this would typically be stored and added with the next vertex.
// However, the current rlvkVertex structure and rlvkAddVertex doesn't take normals.
// This implies either the Vulkan path won't support normals initially with this setup,
// or rlvkVertex/rlvkAddVertex would need to be extended.
// For now, this function will be a no-op for Vulkan or store to a global normal
// similar to texcoord and color, if rlvkVertex is to be expanded.
// Based on the task, rlvkVertex does not include normals. So this is a NO-OP for Vulkan.
#else
float normalx = x;
float normaly = y;
float normalz = z;
@ -1622,15 +1807,20 @@ void rlNormal3f(float x, float y, float z)
RLGL.State.normalx = normalx;
RLGL.State.normaly = normaly;
RLGL.State.normalz = normalz;
#endif
}
// Define one vertex (color)
void rlColor4ub(unsigned char x, unsigned char y, unsigned char z, unsigned char w)
void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
{
RLGL.State.colorr = x;
RLGL.State.colorg = y;
RLGL.State.colorb = z;
RLGL.State.colora = w;
#if defined(GRAPHICS_API_VULKAN)
rlvkSetColor(r, g, b, a);
#else
RLGL.State.colorr = r;
RLGL.State.colorg = g;
RLGL.State.colorb = b;
RLGL.State.colora = a;
#endif
}
// Define one vertex (color)
@ -2265,14 +2455,15 @@ void rlglInit(int width, int height)
// The plan says: "Initialize rlgl default data (buffers and shaders)"
// This might mean setting up RLGL.State for Vulkan mode.
// Init default white texture (Vulkan specific)
// unsigned char pixels[4] = { 255, 255, 255, 255 };
// RLGL.State.defaultTextureId = rlvkLoadTexture(pixels, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1);
// if (RLGL.State.defaultTextureId != 0) TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Default texture loaded successfully (Vulkan)", RLGL.State.defaultTextureId);
// else TRACELOG(LOG_WARNING, "TEXTURE: Failed to load default texture (Vulkan)");
// Init default white texture
// For Vulkan, rlvkInit handles the actual Vulkan texture creation.
// rlgl just needs a conceptual ID. Standard is 1.
RLGL.State.defaultTextureId = 1;
if (RLGL.State.defaultTextureId != 0) TRACELOG(LOG_INFO, "TEXTURE: [ID %i] Default texture_id set for Vulkan path", RLGL.State.defaultTextureId);
else TRACELOG(LOG_WARNING, "TEXTURE: Failed to set default texture_id for Vulkan path");
// Load default shader (Vulkan specific)
// rlLoadShaderDefault(); // This needs to be Vulkan aware
// rlLoadShaderDefault(); // This needs to be Vulkan aware, rlvkInit handles its own shader/pipeline
// RLGL.State.currentShaderId = RLGL.State.defaultShaderId;
// RLGL.State.currentShaderLocs = RLGL.State.defaultShaderLocs;
@ -4676,6 +4867,9 @@ Matrix rlGetMatrixModelview(void)
matrix.m15 = mat[15];
#else
matrix = RLGL.State.modelview;
#endif
#if defined(GRAPHICS_API_VULKAN)
matrix = RLGL.State.modelview;
#endif
return matrix;
}
@ -4704,7 +4898,9 @@ Matrix rlGetMatrixProjection(void)
m.m14 = mat[14];
m.m15 = mat[15];
return m;
#else
#elif defined(GRAPHICS_API_VULKAN)
return RLGL.State.projection;
#else // This implies GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 (and not Vulkan)
return RLGL.State.projection;
#endif
}
@ -4714,11 +4910,15 @@ Matrix rlGetMatrixTransform(void)
{
Matrix mat = rlMatrixIdentity();
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
#if !defined(GRAPHICS_API_VULKAN)
// TODO: Consider possible transform matrices in the RLGL.State.stack
// Is this the right order? or should we start with the first stored matrix instead of the last one?
//Matrix matStackTransform = rlMatrixIdentity();
//for (int i = RLGL.State.stackCounter; i > 0; i--) matStackTransform = rlMatrixMultiply(RLGL.State.stack[i], matStackTransform);
mat = RLGL.State.transform;
#else // defined(GRAPHICS_API_VULKAN)
mat = RLGL.State.transform;
#endif
#endif
return mat;
}
@ -4728,7 +4928,11 @@ Matrix rlGetMatrixProjectionStereo(int eye)
{
Matrix mat = rlMatrixIdentity();
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
#if !defined(GRAPHICS_API_VULKAN)
mat = RLGL.State.projectionStereo[eye];
#else // defined(GRAPHICS_API_VULKAN)
mat = RLGL.State.projectionStereo[eye];
#endif
#endif
return mat;
}
@ -4738,7 +4942,11 @@ Matrix rlGetMatrixViewOffsetStereo(int eye)
{
Matrix mat = rlMatrixIdentity();
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
#if !defined(GRAPHICS_API_VULKAN)
mat = RLGL.State.viewOffsetStereo[eye];
#else // defined(GRAPHICS_API_VULKAN)
mat = RLGL.State.viewOffsetStereo[eye];
#endif
#endif
return mat;
}
@ -4747,7 +4955,11 @@ Matrix rlGetMatrixViewOffsetStereo(int eye)
void rlSetMatrixModelview(Matrix view)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
#if !defined(GRAPHICS_API_VULKAN)
RLGL.State.modelview = view;
#else // defined(GRAPHICS_API_VULKAN)
RLGL.State.modelview = view;
#endif
#endif
}
@ -4755,7 +4967,11 @@ void rlSetMatrixModelview(Matrix view)
void rlSetMatrixProjection(Matrix projection)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
#if !defined(GRAPHICS_API_VULKAN)
RLGL.State.projection = projection;
#else // defined(GRAPHICS_API_VULKAN)
RLGL.State.projection = projection;
#endif
#endif
}
@ -4763,8 +4979,13 @@ void rlSetMatrixProjection(Matrix projection)
void rlSetMatrixProjectionStereo(Matrix right, Matrix left)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
#if !defined(GRAPHICS_API_VULKAN)
RLGL.State.projectionStereo[0] = right;
RLGL.State.projectionStereo[1] = left;
#else // defined(GRAPHICS_API_VULKAN)
RLGL.State.projectionStereo[0] = right;
RLGL.State.projectionStereo[1] = left;
#endif
#endif
}
@ -4772,8 +4993,13 @@ void rlSetMatrixProjectionStereo(Matrix right, Matrix left)
void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left)
{
#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)
#if !defined(GRAPHICS_API_VULKAN)
RLGL.State.viewOffsetStereo[0] = right;
RLGL.State.viewOffsetStereo[1] = left;
#else // defined(GRAPHICS_API_VULKAN)
RLGL.State.viewOffsetStereo[0] = right;
RLGL.State.viewOffsetStereo[1] = left;
#endif
#endif
}