simple_shaderc

Runtime GLSL to SPIR-V Compilation for Eiffel

v1.0.0 MIT Vulkan SDK

Overview

simple_shaderc provides runtime shader compilation capabilities using Google's shaderc library from the Vulkan SDK. It enables Eiffel applications to compile GLSL shader source code to SPIR-V binary format at runtime.

This enables powerful workflows such as:

Part of the Simple Eiffel ecosystem.

Quick Start

Prerequisites

  1. Install Vulkan SDK
  2. Set the environment variable:
    set SIMPLE_EIFFEL=D:\prod

Installation

  1. Add to your ECF file:
    <library name="simple_shaderc" location="$SIMPLE_EIFFEL/simple_shaderc/simple_shaderc.ecf"/>
  2. Copy the shaderc DLL to your application directory:
    copy %VULKAN_SDK%\Bin\shaderc_shared.dll your_app_directory\

Basic Usage

local
    shaderc: SIMPLE_SHADERC
    spirv: detachable MANAGED_POINTER
    glsl: STRING
do
    glsl := "[
        #version 450
        layout(local_size_x = 64) in;
        layout(std430, binding = 0) buffer Data { float values[]; };
        void main() {
            uint idx = gl_GlobalInvocationID.x;
            values[idx] = values[idx] * 2.0;
        }
    ]"

    create shaderc.make
    spirv := shaderc.compile_compute (glsl)

    if attached spirv as spv then
        print ("Compiled to " + spv.count.out + " bytes")
    else
        print ("Error: " + shaderc.last_error)
    end

    shaderc.dispose
end

Key Features

Runtime Compilation

Compile GLSL shaders to SPIR-V at runtime without offline tools.

Multiple Shader Types

Support for compute, vertex, and fragment shaders.

Error Reporting

Detailed compilation error messages for debugging.

SPIR-V Output

Direct binary output ready for Vulkan consumption.

File I/O

Save compiled shaders to disk for caching or distribution.

Vulkan SDK Integration

Uses battle-tested shaderc library from Vulkan SDK.

API Reference

SIMPLE_SHADERC (Facade)

Main entry point for shader compilation.

make

Initialize the shader compiler. Must be called before any compilation.

compile_glsl (a_source: STRING; a_kind: INTEGER): detachable MANAGED_POINTER

Compile GLSL source to SPIR-V bytes. Returns Void on failure.

Preconditions

source_not_empty: not a_source.is_empty
compile_compute (a_source: STRING): detachable MANAGED_POINTER

Convenience method to compile a compute shader.

compile_vertex (a_source: STRING): detachable MANAGED_POINTER

Convenience method to compile a vertex shader.

compile_fragment (a_source: STRING): detachable MANAGED_POINTER

Convenience method to compile a fragment shader.

save_spirv (a_data: MANAGED_POINTER; a_path: STRING)

Save compiled SPIR-V binary to file.

dispose

Release compiler resources. Must be called when done.

Status Queries

Feature Description
last_error Error message from last failed compilation
has_error True if last compilation failed
is_valid True if compiler is properly initialized

Shader Type Constants

Constant Value Description
Shader_vertex 0 Vertex shader for vertex transformation
Shader_fragment 1 Fragment shader for pixel shading
Shader_compute 2 Compute shader for GPU parallel computation

Recipes

Recipe 1: Compile and Save Shader

compile_and_save (a_glsl, a_output_path: STRING)
        -- Compile GLSL and save to file.
    local
        shaderc: SIMPLE_SHADERC
        spirv: detachable MANAGED_POINTER
    do
        create shaderc.make
        spirv := shaderc.compile_compute (a_glsl)

        if attached spirv as spv then
            shaderc.save_spirv (spv, a_output_path)
            print ("Saved: " + a_output_path + "%N")
        else
            print ("Error: " + shaderc.last_error + "%N")
        end

        shaderc.dispose
    end

Recipe 2: Dynamic SDF Shader Generation

generate_sdf_shader: STRING
        -- Generate and compile SDF ray marching shader.
    local
        builder: SDF_GLSL_BUILDER
        shaderc: SIMPLE_SHADERC
        spirv: detachable MANAGED_POINTER
        scene: STRING
    do
        -- Define scene in GLSL
        scene := "[
            float d = 1e10;
            d = opUnion(d, sdSphere(p, vec3(0, 1, 0), 1.0));
            d = opSmoothUnion(d, sdBox(p, vec3(2, 0.5, 0), vec3(0.5)), 0.3);
            return d;
        ]"

        -- Generate full shader
        create builder.make
        Result := builder.generate_basic_shader (scene)

        -- Compile and verify
        create shaderc.make
        spirv := shaderc.compile_compute (Result)

        if spirv = Void then
            print ("Shader error: " + shaderc.last_error)
        end

        shaderc.dispose
    end

Recipe 3: Batch Shader Compilation

compile_all_shaders (a_shaders: HASH_TABLE [STRING, STRING]; a_output_dir: STRING)
        -- Compile multiple shaders (name -> source).
    local
        shaderc: SIMPLE_SHADERC
        spirv: detachable MANAGED_POINTER
    do
        create shaderc.make

        across a_shaders as s loop
            spirv := shaderc.compile_compute (s.item)
            if attached spirv as spv then
                shaderc.save_spirv (spv, a_output_dir + "/" + s.key + ".spv")
                print ("OK: " + s.key + "%N")
            else
                print ("FAIL: " + s.key + " - " + shaderc.last_error + "%N")
            end
        end

        shaderc.dispose
    end

Recipe 4: Integration with simple_vulkan

run_dynamic_compute (a_glsl: STRING)
        -- Compile and execute a compute shader dynamically.
    local
        shaderc: SIMPLE_SHADERC
        vk: SIMPLE_VULKAN
        ctx: VULKAN_CONTEXT
        shader: VULKAN_SHADER
        spirv: detachable MANAGED_POINTER
    do
        -- Compile GLSL to SPIR-V
        create shaderc.make
        spirv := shaderc.compile_compute (a_glsl)

        if attached spirv as spv then
            -- Save to temp file
            shaderc.save_spirv (spv, "temp_compute.spv")

            -- Load with Vulkan
            create vk
            ctx := vk.create_context

            if ctx.is_valid then
                shader := vk.load_shader (ctx, "temp_compute.spv")
                if shader.is_valid then
                    -- Create pipeline and dispatch...
                    print ("Shader loaded successfully!%N")
                    shader.dispose
                end
                ctx.dispose
            end
        else
            print ("Compilation failed: " + shaderc.last_error + "%N")
        end

        shaderc.dispose
    end

Error Handling

The shaderc compiler provides detailed error messages for GLSL syntax and semantic errors:

local
    shaderc: SIMPLE_SHADERC
    spirv: detachable MANAGED_POINTER
    bad_glsl: STRING
do
    bad_glsl := "#version 450%Nvoid main() { undefined_function(); }"

    create shaderc.make
    spirv := shaderc.compile_compute (bad_glsl)

    if spirv = Void then
        print ("Compilation failed:%N")
        print (shaderc.last_error)
        -- Output: shader.glsl:2: error: 'undefined_function' : no matching overloaded function found
    end

    shaderc.dispose
end

Common Errors

Error Cause Solution
"no matching overloaded function" Undefined function or wrong arguments Check function name and parameter types
"local_size_x: no such layout identifier" Wrong shader type for compute shader Use compile_compute for compute shaders
"undeclared identifier" Variable used before declaration Declare variables before use

Performance

Compilation performance on typical hardware:

Operation Time
Compiler initialization <5ms
Simple shader (50 lines) <50ms
Complex SDF shader (300+ lines) ~100ms
Save SPIR-V to disk <1ms

Tip: For production, consider caching compiled SPIR-V files rather than recompiling on every startup.