This is a first part of the Shader Create Info system could be.
A shader create info provides a way to define shader structure, resources
and interfaces. This makes for a quick way to provide backend agnostic
binding informations while also making shader variations easy to declare.
- Clear source input (only one file). Cleans up the GPU api since we can create a
shader from one descriptor
- Resources and interfaces are generated by the backend (much simpler than parsing).
- Bindings are explicit from position in the array.
- GPUShaderInterface becomes a trivial translation of enums and string copy.
- No external dependency to third party lib.
- Cleaner code, less fragmentation of resources in several libs.
- Easy to modify / extend at runtime.
- no parser involve, very easy to code.
- Does not hold any data, can be static and kept on disc.
- Could hold precompiled bytecode for static shaders.
This also includes a new global dependency system.
GLSL shaders can include other sources by using #pragma BLENDER_REQUIRE(...).
This patch already migrated several builtin shaders. Other shaders should be migrated
one at a time, and could be done inside master.
There is a new compile directive `WITH_GPU_SHADER_BUILDER` this is an optional
directive for linting shaders to increase turn around time.
What is remaining:
- pyGPU API {T94975}
- Migration of other shaders. This could be a community effort.
Reviewed By: jbakker
Maniphest Tasks: T94975
Differential Revision: https://developer.blender.org/D13360
596 lines
15 KiB
C++
596 lines
15 KiB
C++
/*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* The Original Code is Copyright (C) 2021 Blender Foundation.
|
|
* All rights reserved.
|
|
*/
|
|
|
|
/** \file
|
|
* \ingroup gpu
|
|
*
|
|
* Descriptior type used to define shader structure, resources and interfaces.
|
|
*
|
|
* Some rule of thumb:
|
|
* - Do not include anything else than this file in each info file.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include "BLI_string_ref.hh"
|
|
#include "BLI_vector.hh"
|
|
#include "GPU_texture.h"
|
|
|
|
namespace blender::gpu::shader {
|
|
|
|
#ifndef GPU_SHADER_CREATE_INFO
|
|
/* Helps intelisense / auto-completion. */
|
|
# define GPU_SHADER_INTERFACE_INFO(_interface, _inst_name) \
|
|
StageInterfaceInfo _interface(#_interface, _inst_name); \
|
|
_interface
|
|
# define GPU_SHADER_CREATE_INFO(_info) \
|
|
ShaderCreateInfo _info(#_info); \
|
|
_info
|
|
#endif
|
|
|
|
enum class Type {
|
|
FLOAT = 0,
|
|
VEC2,
|
|
VEC3,
|
|
VEC4,
|
|
MAT3,
|
|
MAT4,
|
|
UINT,
|
|
UVEC2,
|
|
UVEC3,
|
|
UVEC4,
|
|
INT,
|
|
IVEC2,
|
|
IVEC3,
|
|
IVEC4,
|
|
BOOL,
|
|
};
|
|
|
|
enum class BuiltinBits {
|
|
/** Allow getting barycentic coordinates inside the fragment shader. NOTE: emulated on OpenGL. */
|
|
BARYCENTRIC_COORD = (1 << 0),
|
|
FRAG_COORD = (1 << 2),
|
|
FRONT_FACING = (1 << 4),
|
|
GLOBAL_INVOCATION_ID = (1 << 5),
|
|
INSTANCE_ID = (1 << 6),
|
|
LAYER = (1 << 7),
|
|
LOCAL_INVOCATION_ID = (1 << 8),
|
|
LOCAL_INVOCATION_INDEX = (1 << 9),
|
|
NUM_WORK_GROUP = (1 << 10),
|
|
POINT_COORD = (1 << 11),
|
|
POINT_SIZE = (1 << 12),
|
|
PRIMITIVE_ID = (1 << 13),
|
|
VERTEX_ID = (1 << 14),
|
|
WORK_GROUP_ID = (1 << 15),
|
|
WORK_GROUP_SIZE = (1 << 16),
|
|
};
|
|
ENUM_OPERATORS(BuiltinBits, BuiltinBits::WORK_GROUP_SIZE);
|
|
|
|
/* Samplers & images. */
|
|
enum class ImageType {
|
|
/** Color samplers/image. */
|
|
FLOAT_BUFFER = 0,
|
|
FLOAT_1D,
|
|
FLOAT_1D_ARRAY,
|
|
FLOAT_2D,
|
|
FLOAT_2D_ARRAY,
|
|
FLOAT_3D,
|
|
FLOAT_CUBE,
|
|
FLOAT_CUBE_ARRAY,
|
|
INT_BUFFER,
|
|
INT_1D,
|
|
INT_1D_ARRAY,
|
|
INT_2D,
|
|
INT_2D_ARRAY,
|
|
INT_3D,
|
|
INT_CUBE,
|
|
INT_CUBE_ARRAY,
|
|
UINT_BUFFER,
|
|
UINT_1D,
|
|
UINT_1D_ARRAY,
|
|
UINT_2D,
|
|
UINT_2D_ARRAY,
|
|
UINT_3D,
|
|
UINT_CUBE,
|
|
UINT_CUBE_ARRAY,
|
|
/** Depth samplers (not supported as image). */
|
|
SHADOW_2D,
|
|
SHADOW_2D_ARRAY,
|
|
SHADOW_CUBE,
|
|
SHADOW_CUBE_ARRAY,
|
|
DEPTH_2D,
|
|
DEPTH_2D_ARRAY,
|
|
DEPTH_CUBE,
|
|
DEPTH_CUBE_ARRAY,
|
|
};
|
|
|
|
/* Storage qualifiers. */
|
|
enum class Qualifier {
|
|
RESTRICT = (1 << 0),
|
|
READ_ONLY = (1 << 1),
|
|
WRITE_ONLY = (1 << 2),
|
|
QUALIFIER_MAX = (WRITE_ONLY << 1) - 1,
|
|
};
|
|
ENUM_OPERATORS(Qualifier, Qualifier::QUALIFIER_MAX);
|
|
|
|
enum class Frequency {
|
|
BATCH = 0,
|
|
PASS,
|
|
};
|
|
|
|
/* Dual Source Blending Index. */
|
|
enum class DualBlend {
|
|
NONE = 0,
|
|
SRC_0,
|
|
SRC_1,
|
|
};
|
|
|
|
/* Interpolation qualifiers. */
|
|
enum class Interpolation {
|
|
SMOOTH = 0,
|
|
FLAT,
|
|
NO_PERSPECTIVE,
|
|
};
|
|
|
|
/** Input layout for geometry shader. */
|
|
enum class InputLayout {
|
|
POINTS = 0,
|
|
LINES,
|
|
LINES_ADJACENCY,
|
|
TRIANGLES,
|
|
TRIANGLES_ADJACENCY,
|
|
};
|
|
|
|
/** Output layout for geometry shader. */
|
|
enum class OutputLayout {
|
|
POINTS = 0,
|
|
LINE_STRIP,
|
|
TRIANGLE_STRIP,
|
|
};
|
|
|
|
struct StageInterfaceInfo {
|
|
struct InOut {
|
|
Interpolation interp;
|
|
Type type;
|
|
StringRefNull name;
|
|
};
|
|
|
|
StringRefNull name;
|
|
/** Name of the instance of the block (used to access).
|
|
* Can be empty string (i.e: "") only if not using geometry shader. */
|
|
StringRefNull instance_name;
|
|
/** List of all members of the interface. */
|
|
Vector<InOut> inouts;
|
|
|
|
StageInterfaceInfo(const char *name_, const char *instance_name_)
|
|
: name(name_), instance_name(instance_name_){};
|
|
~StageInterfaceInfo(){};
|
|
|
|
using Self = StageInterfaceInfo;
|
|
|
|
Self &smooth(Type type, StringRefNull _name)
|
|
{
|
|
inouts.append({Interpolation::SMOOTH, type, _name});
|
|
return *(Self *)this;
|
|
}
|
|
|
|
Self &flat(Type type, StringRefNull _name)
|
|
{
|
|
inouts.append({Interpolation::FLAT, type, _name});
|
|
return *(Self *)this;
|
|
}
|
|
|
|
Self &no_perspective(Type type, StringRefNull _name)
|
|
{
|
|
inouts.append({Interpolation::NO_PERSPECTIVE, type, _name});
|
|
return *(Self *)this;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief Describe inputs & outputs, stage interfaces, resources and sources of a shader.
|
|
* If all data is correctly provided, this is all that is needed to create and compile
|
|
* a GPUShader.
|
|
*
|
|
* IMPORTANT: All strings are references only. Make sure all the strings used by a
|
|
* ShaderCreateInfo are not freed until it is consumed or deleted.
|
|
*/
|
|
struct ShaderCreateInfo {
|
|
/** Shader name for debugging. */
|
|
StringRefNull name_;
|
|
/** True if the shader is static and can be precompiled at compile time. */
|
|
bool do_static_compilation_ = false;
|
|
/** If true, all additionaly linked create info will be merged into this one. */
|
|
bool finalized_ = false;
|
|
/**
|
|
* Maximum length of all the resource names including each null terminator.
|
|
* Only for names used by gpu::ShaderInterface.
|
|
*/
|
|
size_t interface_names_size_ = 0;
|
|
/** Only for compute shaders. */
|
|
int local_group_size_[3] = {0, 0, 0};
|
|
|
|
struct VertIn {
|
|
int index;
|
|
Type type;
|
|
StringRefNull name;
|
|
};
|
|
Vector<VertIn> vertex_inputs_;
|
|
|
|
struct GeomIn {
|
|
InputLayout layout;
|
|
};
|
|
GeomIn geom_in_;
|
|
|
|
struct GeomOut {
|
|
OutputLayout layout;
|
|
int max_vertices;
|
|
};
|
|
GeomOut geom_out_;
|
|
|
|
struct FragOut {
|
|
int index;
|
|
Type type;
|
|
DualBlend blend;
|
|
StringRefNull name;
|
|
};
|
|
Vector<FragOut> fragment_outputs_;
|
|
|
|
struct Sampler {
|
|
ImageType type;
|
|
eGPUSamplerState sampler;
|
|
StringRefNull name;
|
|
};
|
|
|
|
struct Image {
|
|
eGPUTextureFormat format;
|
|
ImageType type;
|
|
Qualifier qualifiers;
|
|
StringRefNull name;
|
|
};
|
|
|
|
struct UniformBuf {
|
|
StringRefNull type_name;
|
|
StringRefNull name;
|
|
};
|
|
|
|
struct StorageBuf {
|
|
Qualifier qualifiers;
|
|
StringRefNull type_name;
|
|
StringRefNull name;
|
|
};
|
|
|
|
struct Resource {
|
|
enum BindType {
|
|
UNIFORM_BUFFER = 0,
|
|
STORAGE_BUFFER,
|
|
SAMPLER,
|
|
IMAGE,
|
|
};
|
|
|
|
BindType bind_type;
|
|
int slot;
|
|
union {
|
|
Sampler sampler;
|
|
Image image;
|
|
UniformBuf uniformbuf;
|
|
StorageBuf storagebuf;
|
|
};
|
|
|
|
Resource(BindType type, int _slot) : bind_type(type), slot(_slot){};
|
|
};
|
|
/**
|
|
* Resources are grouped by frequency of change.
|
|
* Pass resources are meants to be valid for the whole pass.
|
|
* Batch resources can be changed in a more granular manner (per object/material).
|
|
* Mis-usage will only produce suboptimal performance.
|
|
*/
|
|
Vector<Resource> pass_resources_, batch_resources_;
|
|
|
|
Vector<StageInterfaceInfo *> vertex_out_interfaces_;
|
|
Vector<StageInterfaceInfo *> geometry_out_interfaces_;
|
|
|
|
struct PushConst {
|
|
int index;
|
|
Type type;
|
|
StringRefNull name;
|
|
int array_size;
|
|
};
|
|
|
|
Vector<PushConst> push_constants_;
|
|
|
|
/* Sources for resources type definitions. */
|
|
Vector<StringRefNull> typedef_sources_;
|
|
|
|
StringRefNull vertex_source_, geometry_source_, fragment_source_, compute_source_;
|
|
|
|
Vector<std::array<StringRefNull, 2>> defines_;
|
|
/**
|
|
* Name of other infos to recursively merge with this one.
|
|
* No data slot must overlap otherwise we throw an error.
|
|
*/
|
|
Vector<StringRefNull> additional_infos_;
|
|
|
|
public:
|
|
ShaderCreateInfo(const char *name) : name_(name){};
|
|
~ShaderCreateInfo(){};
|
|
|
|
using Self = ShaderCreateInfo;
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Shaders in/outs (fixed function pipeline config)
|
|
* \{ */
|
|
|
|
Self &vertex_in(int slot, Type type, StringRefNull name)
|
|
{
|
|
vertex_inputs_.append({slot, type, name});
|
|
interface_names_size_ += name.size() + 1;
|
|
return *(Self *)this;
|
|
}
|
|
|
|
Self &vertex_out(StageInterfaceInfo &interface)
|
|
{
|
|
vertex_out_interfaces_.append(&interface);
|
|
return *(Self *)this;
|
|
}
|
|
|
|
Self &geometry_layout(InputLayout layout_in, OutputLayout layout_out, int max_vertices)
|
|
{
|
|
geom_in_.layout = layout_in;
|
|
geom_out_.layout = layout_out;
|
|
geom_out_.max_vertices = max_vertices;
|
|
return *(Self *)this;
|
|
}
|
|
|
|
/* Only needed if geometry shader is enabled. */
|
|
Self &geometry_out(StageInterfaceInfo &interface)
|
|
{
|
|
geometry_out_interfaces_.append(&interface);
|
|
return *(Self *)this;
|
|
}
|
|
|
|
Self &fragment_out(int slot, Type type, StringRefNull name, DualBlend blend = DualBlend::NONE)
|
|
{
|
|
fragment_outputs_.append({slot, type, blend, name});
|
|
return *(Self *)this;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Resources bindings points
|
|
* \{ */
|
|
|
|
Self &uniform_buf(int slot,
|
|
StringRefNull type_name,
|
|
StringRefNull name,
|
|
Frequency freq = Frequency::PASS)
|
|
{
|
|
Resource res(Resource::BindType::UNIFORM_BUFFER, slot);
|
|
res.uniformbuf.name = name;
|
|
res.uniformbuf.type_name = type_name;
|
|
((freq == Frequency::PASS) ? pass_resources_ : batch_resources_).append(res);
|
|
interface_names_size_ += name.size() + 1;
|
|
return *(Self *)this;
|
|
}
|
|
|
|
Self &storage_buf(int slot,
|
|
Qualifier qualifiers,
|
|
StringRefNull type_name,
|
|
StringRefNull name,
|
|
Frequency freq = Frequency::PASS)
|
|
{
|
|
Resource res(Resource::BindType::STORAGE_BUFFER, slot);
|
|
res.storagebuf.qualifiers = qualifiers;
|
|
res.storagebuf.type_name = type_name;
|
|
res.storagebuf.name = name;
|
|
((freq == Frequency::PASS) ? pass_resources_ : batch_resources_).append(res);
|
|
interface_names_size_ += name.size() + 1;
|
|
return *(Self *)this;
|
|
}
|
|
|
|
Self &image(int slot,
|
|
eGPUTextureFormat format,
|
|
Qualifier qualifiers,
|
|
ImageType type,
|
|
StringRefNull name,
|
|
Frequency freq = Frequency::PASS)
|
|
{
|
|
Resource res(Resource::BindType::IMAGE, slot);
|
|
res.image.format = format;
|
|
res.image.qualifiers = qualifiers;
|
|
res.image.type = type;
|
|
res.image.name = name;
|
|
((freq == Frequency::PASS) ? pass_resources_ : batch_resources_).append(res);
|
|
interface_names_size_ += name.size() + 1;
|
|
return *(Self *)this;
|
|
}
|
|
|
|
Self &sampler(int slot,
|
|
ImageType type,
|
|
StringRefNull name,
|
|
Frequency freq = Frequency::PASS,
|
|
eGPUSamplerState sampler = (eGPUSamplerState)-1)
|
|
{
|
|
Resource res(Resource::BindType::SAMPLER, slot);
|
|
res.sampler.type = type;
|
|
res.sampler.name = name;
|
|
res.sampler.sampler = sampler;
|
|
((freq == Frequency::PASS) ? pass_resources_ : batch_resources_).append(res);
|
|
interface_names_size_ += name.size() + 1;
|
|
return *(Self *)this;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Shader Source
|
|
* \{ */
|
|
|
|
Self &vertex_source(StringRefNull filename)
|
|
{
|
|
vertex_source_ = filename;
|
|
return *(Self *)this;
|
|
}
|
|
|
|
Self &geometry_source(StringRefNull filename)
|
|
{
|
|
geometry_source_ = filename;
|
|
return *(Self *)this;
|
|
}
|
|
|
|
Self &fragment_source(StringRefNull filename)
|
|
{
|
|
fragment_source_ = filename;
|
|
return *(Self *)this;
|
|
}
|
|
|
|
Self &compute_source(StringRefNull filename)
|
|
{
|
|
compute_source_ = filename;
|
|
return *(Self *)this;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Push constants
|
|
*
|
|
* Data managed by GPUShader. Can be set through uniform functions. Must be less than 128bytes.
|
|
* One slot represents 4bytes. Each element needs to have enough empty space left after it.
|
|
* example:
|
|
* [0] = PUSH_CONSTANT(MAT4, "ModelMatrix"),
|
|
* ---- 16 slots occupied by ModelMatrix ----
|
|
* [16] = PUSH_CONSTANT(VEC4, "color"),
|
|
* ---- 4 slots occupied by color ----
|
|
* [20] = PUSH_CONSTANT(BOOL, "srgbToggle"),
|
|
* The maximum slot is 31.
|
|
* \{ */
|
|
|
|
Self &push_constant(int slot, Type type, StringRefNull name, int array_size = 0)
|
|
{
|
|
BLI_assert_msg(name.find("[") == -1,
|
|
"Array syntax is forbidden for push constants."
|
|
"Use the array_size parameter instead.");
|
|
push_constants_.append({slot, type, name, array_size});
|
|
interface_names_size_ += name.size() + 1;
|
|
return *(Self *)this;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Compute shaders Local Group Size
|
|
* \{ */
|
|
|
|
Self &local_group_size(int x, int y = 1, int z = 1)
|
|
{
|
|
local_group_size_[0] = x;
|
|
local_group_size_[1] = y;
|
|
local_group_size_[2] = z;
|
|
return *(Self *)this;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Defines
|
|
* \{ */
|
|
|
|
Self &define(StringRefNull name, StringRefNull value = "")
|
|
{
|
|
defines_.append({name, value});
|
|
return *(Self *)this;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Defines
|
|
* \{ */
|
|
|
|
Self &do_static_compilation(bool value)
|
|
{
|
|
do_static_compilation_ = value;
|
|
return *(Self *)this;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Additional Create Info
|
|
*
|
|
* Used to share parts of the infos that are common to many shaders.
|
|
* \{ */
|
|
|
|
Self &additional_info(StringRefNull info_name0,
|
|
StringRefNull info_name1 = "",
|
|
StringRefNull info_name2 = "",
|
|
StringRefNull info_name3 = "",
|
|
StringRefNull info_name4 = "")
|
|
{
|
|
additional_infos_.append(info_name0);
|
|
if (!info_name1.is_empty()) {
|
|
additional_infos_.append(info_name1);
|
|
}
|
|
if (!info_name2.is_empty()) {
|
|
additional_infos_.append(info_name2);
|
|
}
|
|
if (!info_name3.is_empty()) {
|
|
additional_infos_.append(info_name3);
|
|
}
|
|
if (!info_name4.is_empty()) {
|
|
additional_infos_.append(info_name4);
|
|
}
|
|
return *(Self *)this;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Typedef Sources
|
|
*
|
|
* Some resource declarations might need some special structure defined.
|
|
* Adding a file using typedef_source will include it before the resource
|
|
* and interface definitions.
|
|
* \{ */
|
|
|
|
Self &typedef_source(StringRefNull filename)
|
|
{
|
|
typedef_sources_.append(filename);
|
|
return *(Self *)this;
|
|
}
|
|
|
|
/** \} */
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/** \name Recursive evaluation.
|
|
*
|
|
* Flatten all dependency so that this descriptor contains all the data from the additional
|
|
* descriptors. This avoids tedious traversal in shader source creation.
|
|
* \{ */
|
|
|
|
/* WARNING: Recursive. */
|
|
void finalize();
|
|
|
|
/** \} */
|
|
};
|
|
|
|
} // namespace blender::gpu::shader
|