LukasBanana / LLGL

Low Level Graphics Library (LLGL) is a thin abstraction layer for the modern graphics APIs OpenGL, Direct3D, Vulkan, and Metal
BSD 3-Clause "New" or "Revised" License
2.05k stars 139 forks source link

Refactor shader interface #16

Closed LukasBanana closed 6 years ago

LukasBanana commented 6 years ago

Shader and ShaderProgram interfaces should be refactored to follow the 'descriptor at creation time' principle. Attaching, detaching, and re-compiling shaders does not work properly for Direct3D and Vulkan. Moreover, it has only been adopted from the deprecated interface of OpenGL (like with glAttachShader).

In the new interface, the shaders must be compiled at creation time:

enum class ShaderSourceType {
    CodeString,   // Refers to <const char[sourceSize + 1]> describing shader code (with '\0')
    CodeFile,     // Refers to <const char[sourceSize + 1]> describing shader code file (with '\0')
    BinaryBuffer, // Refers to <const char[sourceSize]> describing shader binary
    BinaryFile    // Refers to <const char[sourceSize + 1]> describing shader binary file (with '\0')
};

struct ShaderDescriptor {
    ShaderDescriptor();
    ShaderDescriptor(
        ShaderType  type,
        const char* source,
        std::size_t sourceSize = 0
    );
    ShaderDescriptor(
        ShaderType  type,
        const char* source,
        std::size_t sourceSize,
        const char* entryPoint,
        const char* profile
    );

    struct StreamOutput {
        StreamOutputFormat format;
    };

    ShaderType       type         = ShaderType::Undefined;
    const char*      source       = nullptr; // points to the source code or filename
    std::size_t      sourceSize   = 0;       // length of the source (excluding a null terminator!)
    ShaderSourceType sourceType   = ShaderSourceType::CodeFile;
    const char*      entryPoint   = nullptr;
    const char*      profile      = nullptr; // previously called target, but profile fits better
    long             flags        = 0;
    StreamOutput     streamOutput;
};

struct GraphicsShaderProgramDescriptor {
    std::vector<VertexFormat> vertexFormats;
    Shader*                   vertexShader         = nullptr;
    Shader*                   tessControlShader    = nullptr;
    Shader*                   tessEvaluationShader = nullptr;
    Shader*                   geometryShader       = nullptr;
    Shader*                   fragmentShader       = nullptr;
};

struct ComputeShaderProgramDescriptor {
    Shader* computeShader = nullptr;
};

class RenderSystem {
    Shader* CreateShader(const ShaderDescriptor& desc);
    ShaderProgram* CreateShaderProgram(const GraphicsShaderProgramDescriptor& desc);
    ShaderProgram* CreateShaderProgram(const ComputeShaderProgramDescriptor& desc);
    /* ... */
};

Functions that will be removed from the interfaces:

class Shader {
    bool Compile(const std::string&, const ShaderDescriptor&);
    bool LoadBinary(std::vector<char>&&, const ShaderDescriptor&);
    /* ... */
};
class ShaderProgram {
    void AttachShader(Shader&);
    void DetachAll();
    bool LinkShaders();
    void BuildInputLayout(std::uint32_t, const VertexFormat*);
    /* ... */
};

Functions that will be added to the interfaces:

class Shader {
    bool HasErrors() const;
    /* ... */
};
class ShaderProgram {
    bool HasErrors() const;
    /* ... */
};

Example usage:

LLGL::GraphicsShaderProgramDescriptor myGfxProgramDesc;
{
    myGfxProgramDesc.vertexFormats  = { myVertexFormat };
    myGfxProgramDesc.vertexShader   = myRenderer->CreateShader({ LLGL::ShaderType::Vertex,   "myVertexShader.vert"   });
    myGfxProgramDesc.fragmentShader = myRenderer->CreateShader({ LLGL::ShaderType::Fragment, "myFragmentShader.frag" });
}
auto myShaderProgram = myRenderer->CreateShaderProgram(myGfxProgramDesc);
LukasBanana commented 6 years ago

Done with 47c499a.