GPU: Move more linting and processing of GLSL to compile time

The goal is to reduce the startup time cost of
all of these parsing and string replacement.

All comments are now stripped at compile time.
This comment check added noticeable slowdown at
startup in debug builds and during preprocessing.

Put all metadatas between start and end token.
Use very simple parsing using `StringRef` and
hash all identifiers.

Move all the complexity to the preprocessor that
massagess the metadata into a well expected input
to the runtime parser.

All identifiers are compile time hashed so that no string
comparison is made at runtime.

Speed up the source loading:
- from 10ms to 1.6ms (6.25x speedup) in release
- from 194ms to 6ms (32.3x speedup) in debug

Follow up #129009

Pull Request: https://projects.blender.org/blender/blender/pulls/128927
This commit is contained in:
Clément Foucault
2024-10-15 19:47:30 +02:00
committed by Clément Foucault
parent 610f85d9f8
commit 62826931b0
32 changed files with 745 additions and 2077 deletions

View File

@@ -575,7 +575,6 @@ set(GLSL_SRC
intern/shaders/common_attribute_lib.glsl
intern/shaders/common_colormanagement_lib.glsl
intern/shaders/common_debug_draw_lib.glsl
intern/shaders/common_debug_print_lib.glsl
intern/shaders/common_debug_shape_lib.glsl
intern/shaders/common_fullscreen_vert.glsl
intern/shaders/common_fxaa_lib.glsl
@@ -605,8 +604,6 @@ set(GLSL_SRC
intern/shaders/draw_command_generate_comp.glsl
intern/shaders/draw_debug_draw_display_frag.glsl
intern/shaders/draw_debug_draw_display_vert.glsl
intern/shaders/draw_debug_print_display_frag.glsl
intern/shaders/draw_debug_print_display_vert.glsl
intern/shaders/draw_intersect_lib.glsl
intern/shaders/draw_math_geom_lib.glsl
intern/shaders/draw_model_lib.glsl

View File

@@ -37,9 +37,14 @@ vec3 debug_random_color(int v)
void debug_tile_print(ShadowTileData tile, ivec4 tile_coord)
{
#ifdef DRW_DEBUG_PRINT
drw_print("Tile (", tile_coord.x, ",", tile_coord.y, ") in Tilemap ", tile_coord.z, " : ");
drw_print(tile.page);
drw_print(tile.cache_index);
printf("Tile (%u, %u) in Tilemap %u: page(%u, %u, %u), cache_index %u",
tile_coord.x,
tile_coord.y,
tile_coord.z,
tile.page.x,
tile.page.y,
tile.page.z,
tile.cache_index);
#endif
}

View File

@@ -17,7 +17,7 @@ typedef enum gpLightType gpLightType;
# endif
#endif
enum gpMaterialFlag {
enum gpMaterialFlag : uint32_t {
GP_FLAG_NONE = 0u,
GP_STROKE_ALIGNMENT_STROKE = 1u,
GP_STROKE_ALIGNMENT_OBJECT = 2u,
@@ -39,7 +39,7 @@ enum gpMaterialFlag {
GP_FILL_GRADIENT_USE | GP_FILL_GRADIENT_RADIAL | GP_FILL_HOLDOUT),
};
enum gpLightType {
enum gpLightType : uint32_t {
GP_LIGHT_TYPE_POINT = 0u,
GP_LIGHT_TYPE_SPOT = 1u,
GP_LIGHT_TYPE_SUN = 2u,

View File

@@ -23,7 +23,7 @@ vec3 gpencil_lighting(void)
}
vec3 L = gp_lights[i]._position - gp_interp.pos;
float vis = 1.0;
gpLightType type = floatBitsToUint(gp_lights[i]._type);
gpLightType type = gpLightType(floatBitsToUint(gp_lights[i]._type));
/* Spot Attenuation. */
if (type == GP_LIGHT_TYPE_SPOT) {
mat3 rot_scale = mat3(gp_lights[i]._right, gp_lights[i]._up, gp_lights[i]._forward);

View File

@@ -36,7 +36,7 @@ void main()
ivec4 ma1 = floatBitsToInt(texelFetch(gp_pos_tx, gpencil_stroke_point_id() * 3 + 1));
gpMaterial gp_mat = gp_materials[ma1.x + gpMaterialOffset];
gpMaterialFlag gp_flag = floatBitsToUint(gp_mat._flag);
gpMaterialFlag gp_flag = gpMaterialFlag(floatBitsToUint(gp_mat._flag));
gl_Position = gpencil_vertex(vec4(viewportSize, 1.0 / viewportSize),
gp_flag,

View File

@@ -66,31 +66,17 @@ DebugDraw::DebugDraw()
void DebugDraw::init()
{
cpu_print_buf_.command.vertex_len = 0;
cpu_print_buf_.command.vertex_first = 0;
cpu_print_buf_.command.instance_len = 1;
cpu_print_buf_.command.instance_first_array = 0;
cpu_draw_buf_.command.vertex_len = 0;
cpu_draw_buf_.command.vertex_first = 0;
cpu_draw_buf_.command.instance_len = 1;
cpu_draw_buf_.command.instance_first_array = 0;
gpu_print_buf_.command.vertex_len = 0;
gpu_print_buf_.command.vertex_first = 0;
gpu_print_buf_.command.instance_len = 1;
gpu_print_buf_.command.instance_first_array = 0;
gpu_print_buf_used = false;
gpu_draw_buf_.command.vertex_len = 0;
gpu_draw_buf_.command.vertex_first = 0;
gpu_draw_buf_.command.instance_len = 1;
gpu_draw_buf_.command.instance_first_array = 0;
gpu_draw_buf_used = false;
print_col_ = 0;
print_row_ = 0;
modelmat_reset();
}
@@ -113,15 +99,6 @@ GPUStorageBuf *DebugDraw::gpu_draw_buf_get()
return gpu_draw_buf_;
}
GPUStorageBuf *DebugDraw::gpu_print_buf_get()
{
if (!gpu_print_buf_used) {
gpu_print_buf_used = true;
gpu_print_buf_.push_update();
}
return gpu_print_buf_;
}
/** \} */
/* -------------------------------------------------------------------- */
@@ -215,113 +192,11 @@ void DebugDraw::draw_point(const float3 center, float radius, const float4 color
/** \} */
/* -------------------------------------------------------------------- */
/** \name Print functions
* \{ */
template<> void DebugDraw::print_value<uint>(const uint &value)
{
print_value_uint(value, false, false, true);
}
template<> void DebugDraw::print_value<int>(const int &value)
{
print_value_uint(uint(abs(value)), false, (value < 0), false);
}
template<> void DebugDraw::print_value<bool>(const bool &value)
{
print_string(value ? "true " : "false");
}
template<> void DebugDraw::print_value<float>(const float &val)
{
std::stringstream ss;
ss << std::setw(12) << std::to_string(val);
print_string(ss.str());
}
template<> void DebugDraw::print_value<double>(const double &val)
{
print_value(float(val));
}
template<> void DebugDraw::print_value_hex<uint>(const uint &value)
{
print_value_uint(value, true, false, false);
}
template<> void DebugDraw::print_value_hex<int>(const int &value)
{
print_value_uint(uint(value), true, false, false);
}
template<> void DebugDraw::print_value_hex<float>(const float &value)
{
print_value_uint(*reinterpret_cast<const uint *>(&value), true, false, false);
}
template<> void DebugDraw::print_value_hex<double>(const double &val)
{
print_value_hex(float(val));
}
template<> void DebugDraw::print_value_binary<uint>(const uint &value)
{
print_value_binary(value);
}
template<> void DebugDraw::print_value_binary<int>(const int &value)
{
print_value_binary(uint(value));
}
template<> void DebugDraw::print_value_binary<float>(const float &value)
{
print_value_binary(*reinterpret_cast<const uint *>(&value));
}
template<> void DebugDraw::print_value_binary<double>(const double &val)
{
print_value_binary(float(val));
}
template<> void DebugDraw::print_value<float2>(const float2 &value)
{
print_no_endl("float2(", value[0], ", ", value[1], ")");
}
template<> void DebugDraw::print_value<float3>(const float3 &value)
{
print_no_endl("float3(", value[0], ", ", value[1], ", ", value[1], ")");
}
template<> void DebugDraw::print_value<float4>(const float4 &value)
{
print_no_endl("float4(", value[0], ", ", value[1], ", ", value[2], ", ", value[3], ")");
}
template<> void DebugDraw::print_value<int2>(const int2 &value)
{
print_no_endl("int2(", value[0], ", ", value[1], ")");
}
template<> void DebugDraw::print_value<int3>(const int3 &value)
{
print_no_endl("int3(", value[0], ", ", value[1], ", ", value[1], ")");
}
template<> void DebugDraw::print_value<int4>(const int4 &value)
{
print_no_endl("int4(", value[0], ", ", value[1], ", ", value[2], ", ", value[3], ")");
}
template<> void DebugDraw::print_value<uint2>(const uint2 &value)
{
print_no_endl("uint2(", value[0], ", ", value[1], ")");
}
template<> void DebugDraw::print_value<uint3>(const uint3 &value)
{
print_no_endl("uint3(", value[0], ", ", value[1], ", ", value[1], ")");
}
template<> void DebugDraw::print_value<uint4>(const uint4 &value)
{
print_no_endl("uint4(", value[0], ", ", value[1], ", ", value[2], ", ", value[3], ")");
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Internals
*
* IMPORTANT: All of these are copied from the shader libraries (`common_debug_draw_lib.glsl` &
* `common_debug_print_lib.glsl`). They need to be kept in sync to write the same data.
* IMPORTANT: All of these are copied from the shader libraries (`common_debug_draw_lib.glsl`).
* They need to be kept in sync to write the same data.
* \{ */
void DebugDraw::draw_line(float3 v1, float3 v2, uint color)
@@ -357,149 +232,6 @@ DRWDebugVert DebugDraw::vert_pack(float3 pos, uint color)
return vert;
}
void DebugDraw::print_newline()
{
print_col_ = 0u;
print_row_ = ++cpu_print_buf_.command.instance_first_array;
}
void DebugDraw::print_string_start(uint len)
{
/* Break before word. */
if (print_col_ + len > DRW_DEBUG_PRINT_WORD_WRAP_COLUMN) {
print_newline();
}
}
/* Copied from gpu_shader_dependency. */
void DebugDraw::print_string(std::string str)
{
size_t len_before_pad = str.length();
/* Pad string to uint size to avoid out of bound reads. */
while (str.length() % 4 != 0) {
str += " ";
}
print_string_start(len_before_pad);
for (size_t i = 0; i < len_before_pad; i += 4) {
union {
uint8_t chars[4];
uint32_t word;
};
chars[0] = *(reinterpret_cast<const uint8_t *>(str.c_str()) + i + 0);
chars[1] = *(reinterpret_cast<const uint8_t *>(str.c_str()) + i + 1);
chars[2] = *(reinterpret_cast<const uint8_t *>(str.c_str()) + i + 2);
chars[3] = *(reinterpret_cast<const uint8_t *>(str.c_str()) + i + 3);
if (i + 4 > len_before_pad) {
chars[len_before_pad - i] = '\0';
}
print_char4(word);
}
}
/* Keep in sync with shader. */
void DebugDraw::print_char4(uint data)
{
/* Convert into char stream. */
for (; data != 0u; data >>= 8u) {
uint char1 = data & 0xFFu;
/* Check for null terminator. */
if (char1 == 0x00) {
break;
}
/* NOTE: Do not skip the header manually like in GPU. */
uint cursor = cpu_print_buf_.command.vertex_len++;
if (cursor < DRW_DEBUG_PRINT_MAX) {
/* For future usage. (i.e: Color) */
uint flags = 0u;
uint col = print_col_++;
uint print_header = (flags << 24u) | (print_row_ << 16u) | (col << 8u);
cpu_print_buf_.char_array[cursor] = print_header | char1;
/* Break word. */
if (print_col_ > DRW_DEBUG_PRINT_WORD_WRAP_COLUMN) {
print_newline();
}
}
}
}
void DebugDraw::print_append_char(uint char1, uint &char4)
{
char4 = (char4 << 8u) | char1;
}
void DebugDraw::print_append_digit(uint digit, uint &char4)
{
const uint char_A = 0x41u;
const uint char_0 = 0x30u;
bool is_hexadecimal = digit > 9u;
char4 = (char4 << 8u) | (is_hexadecimal ? (char_A + digit - 10u) : (char_0 + digit));
}
void DebugDraw::print_append_space(uint &char4)
{
char4 = (char4 << 8u) | 0x20u;
}
void DebugDraw::print_value_binary(uint value)
{
print_string("0b");
print_string_start(10u * 4u);
uint digits[10] = {0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u};
uint digit = 0u;
for (uint i = 0u; i < 32u; i++) {
print_append_digit(((value >> i) & 1u), digits[digit / 4u]);
digit++;
if ((i % 4u) == 3u) {
print_append_space(digits[digit / 4u]);
digit++;
}
}
/* Numbers are written from right to left. So we need to reverse the order. */
for (int j = 9; j >= 0; j--) {
print_char4(digits[j]);
}
}
void DebugDraw::print_value_uint(uint value,
const bool hex,
bool is_negative,
const bool is_unsigned)
{
print_string_start(3u * 4u);
const uint blank_value = hex ? 0x30303030u : 0x20202020u;
const uint prefix = hex ? 0x78302020u : 0x20202020u;
uint digits[3] = {blank_value, blank_value, prefix};
const uint base = hex ? 16u : 10u;
uint digit = 0u;
/* Add `u` suffix. */
if (is_unsigned) {
print_append_char('u', digits[digit / 4u]);
digit++;
}
/* Number's digits. */
for (; value != 0u || digit == uint(is_unsigned); value /= base) {
print_append_digit(value % base, digits[digit / 4u]);
digit++;
}
/* Add negative sign. */
if (is_negative) {
print_append_char('-', digits[digit / 4u]);
digit++;
}
/* Need to pad to uint alignment because we are issuing chars in "reverse". */
for (uint i = digit % 4u; i < 4u && i > 0u; i++) {
print_append_space(digits[digit / 4u]);
digit++;
}
/* Numbers are written from right to left. So we need to reverse the order. */
for (int j = 2; j >= 0; j--) {
print_char4(digits[j]);
}
}
/** \} */
/* -------------------------------------------------------------------- */
@@ -542,47 +274,11 @@ void DebugDraw::display_lines()
GPU_debug_group_end();
}
void DebugDraw::display_prints()
{
if (cpu_print_buf_.command.vertex_len == 0 && gpu_print_buf_used == false) {
return;
}
GPU_debug_group_begin("Prints");
cpu_print_buf_.push_update();
drw_state_set(DRW_STATE_WRITE_COLOR | DRW_STATE_PROGRAM_POINT_SIZE);
gpu::Batch *batch = drw_cache_procedural_points_get();
GPUShader *shader = DRW_shader_debug_print_display_get();
GPU_batch_set_shader(batch, shader);
float f_viewport[4];
GPU_viewport_size_get_f(f_viewport);
GPU_shader_uniform_2fv(shader, "viewport_size", &f_viewport[2]);
if (gpu_print_buf_used) {
GPU_debug_group_begin("GPU");
GPU_storagebuf_bind(gpu_print_buf_, DRW_DEBUG_PRINT_SLOT);
GPU_batch_draw_indirect(batch, gpu_print_buf_, 0);
GPU_storagebuf_unbind(gpu_print_buf_);
GPU_debug_group_end();
}
GPU_debug_group_begin("CPU");
GPU_storagebuf_bind(cpu_print_buf_, DRW_DEBUG_PRINT_SLOT);
GPU_batch_draw_indirect(batch, cpu_print_buf_, 0);
GPU_storagebuf_unbind(cpu_print_buf_);
GPU_debug_group_end();
GPU_debug_group_end();
}
void DebugDraw::display_to_view()
{
GPU_debug_group_begin("DebugDraw");
display_lines();
/* Print 3D shapes before text to avoid overlaps. */
display_prints();
/* Init again so we don't draw the same thing twice. */
init();
@@ -647,11 +343,6 @@ GPUStorageBuf *drw_debug_gpu_draw_buf_get()
return reinterpret_cast<blender::draw::DebugDraw *>(DST.debug)->gpu_draw_buf_get();
}
GPUStorageBuf *drw_debug_gpu_print_buf_get()
{
return reinterpret_cast<blender::draw::DebugDraw *>(DST.debug)->gpu_print_buf_get();
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@@ -8,7 +8,7 @@
* \brief Simple API to draw debug shapes and log in the viewport.
*
* Both CPU and GPU implementation are supported and symmetrical (meaning GPU shader can use it
* too, see common_debug_print/draw_lib.glsl).
* too, see common_draw_lib.glsl).
*
* NOTE: CPU logging will overlap GPU logging on screen as it is drawn after.
*/
@@ -31,36 +31,21 @@ namespace blender::draw {
#define drw_debug_point(...) DRW_debug_get()->draw_point(__VA_ARGS__)
#define drw_debug_matrix(...) DRW_debug_get()->draw_matrix(__VA_ARGS__)
#define drw_debug_matrix_as_bbox(...) DRW_debug_get()->draw_matrix_as_bbox(__VA_ARGS__)
#define drw_print(...) DRW_debug_get()->print(__VA_ARGS__)
#define drw_print_hex(...) DRW_debug_get()->print_hex(__VA_ARGS__)
#define drw_print_binary(...) DRW_debug_get()->print_binary(__VA_ARGS__)
#define drw_print_no_endl(...) DRW_debug_get()->print_no_endl(__VA_ARGS__)
/* Will log variable along with its name, like the shader version of print(). */
#define drw_print_id(v_) DRW_debug_get()->print(#v_, "= ", v_)
#define drw_print_id_no_endl(v_) DRW_debug_get()->print_no_endl(#v_, "= ", v_)
class DebugDraw {
private:
using DebugDrawBuf = StorageBuffer<DRWDebugDrawBuffer>;
using DebugPrintBuf = StorageBuffer<DRWDebugPrintBuffer>;
/** Data buffers containing all verts or chars to draw. */
DebugDrawBuf cpu_draw_buf_ = {"DebugDrawBuf-CPU"};
DebugDrawBuf gpu_draw_buf_ = {"DebugDrawBuf-GPU"};
DebugPrintBuf cpu_print_buf_ = {"DebugPrintBuf-CPU"};
DebugPrintBuf gpu_print_buf_ = {"DebugPrintBuf-GPU"};
/** True if the gpu buffer have been requested and may contain data to draw. */
bool gpu_print_buf_used = false;
bool gpu_draw_buf_used = false;
/** Matrix applied to all points before drawing. Could be a stack if needed. */
float4x4 model_mat_;
/** Precomputed shapes verts. */
Vector<float3> sphere_verts_;
Vector<float3> point_verts_;
/** Cursor position for print functionality. */
uint print_col_ = 0;
uint print_row_ = 0;
public:
DebugDraw();
@@ -104,66 +89,10 @@ class DebugDraw {
*/
void display_to_view();
/**
* Log variable or strings inside the viewport.
* Using a unique non string argument will print the variable name with it.
* Concatenate by using multiple arguments. i.e: `print("Looped ", n, "times.")`.
*/
template<typename... Ts> void print(StringRefNull str, Ts... args)
{
print_no_endl(str, args...);
print_newline();
}
template<typename T> void print(const T &value)
{
print_value(value);
print_newline();
}
template<typename T> void print_hex(const T &value)
{
print_value_hex(value);
print_newline();
}
template<typename T> void print_binary(const T &value)
{
print_value_binary(value);
print_newline();
}
/**
* Same as `print()` but does not finish the line.
*/
void print_no_endl(std::string arg)
{
print_string(arg);
}
void print_no_endl(StringRef arg)
{
print_string(arg);
}
void print_no_endl(StringRefNull arg)
{
print_string(arg);
}
void print_no_endl(char const *arg)
{
print_string(StringRefNull(arg));
}
template<typename T> void print_no_endl(T arg)
{
print_value(arg);
}
template<typename T, typename... Ts> void print_no_endl(T arg, Ts... args)
{
print_no_endl(arg);
print_no_endl(args...);
}
/**
* Not to be called by user. Should become private.
*/
GPUStorageBuf *gpu_draw_buf_get();
GPUStorageBuf *gpu_print_buf_get();
private:
uint color_pack(float4 color);
@@ -171,22 +100,7 @@ class DebugDraw {
void draw_line(float3 v1, float3 v2, uint color);
void print_newline();
void print_string_start(uint len);
void print_string(std::string str);
void print_char4(uint data);
void print_append_char(uint char1, uint &char4);
void print_append_digit(uint digit, uint &char4);
void print_append_space(uint &char4);
void print_value_binary(uint value);
void print_value_uint(uint value, const bool hex, bool is_negative, const bool is_unsigned);
template<typename T> void print_value(const T &value);
template<typename T> void print_value_hex(const T &value);
template<typename T> void print_value_binary(const T &value);
void display_lines();
void display_prints();
};
} // namespace blender::draw

View File

@@ -139,7 +139,6 @@ void Manager::debug_bind()
return;
}
GPU_storagebuf_bind(drw_debug_gpu_draw_buf_get(), DRW_DEBUG_DRAW_SLOT);
GPU_storagebuf_bind(drw_debug_gpu_print_buf_get(), DRW_DEBUG_PRINT_SLOT);
# ifndef DISABLE_DEBUG_SHADER_PRINT_BARRIER
/* Add a barrier to allow multiple shader writing to the same buffer. */
GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE);

View File

@@ -705,7 +705,6 @@ void drw_debug_draw();
void drw_debug_init();
void drw_debug_module_free(DRWDebugModule *module);
GPUStorageBuf *drw_debug_gpu_draw_buf_get();
GPUStorageBuf *drw_debug_gpu_print_buf_get();
eDRWCommandType command_type_get(const uint64_t *command_type_bits, int index);

View File

@@ -59,14 +59,6 @@ GPUShader *DRW_shader_curves_refine_get(blender::draw::CurvesEvalShader type)
return e_data.hair_refine_sh[type];
}
GPUShader *DRW_shader_debug_print_display_get()
{
if (e_data.debug_print_display_sh == nullptr) {
e_data.debug_print_display_sh = GPU_shader_create_from_info_name("draw_debug_print_display");
}
return e_data.debug_print_display_sh;
}
GPUShader *DRW_shader_debug_draw_display_get()
{
if (e_data.debug_draw_display_sh == nullptr) {

View File

@@ -19,7 +19,6 @@ GPUShader *DRW_shader_hair_refine_get(ParticleRefineShader refinement);
GPUShader *DRW_shader_curves_refine_get(blender::draw::CurvesEvalShader type);
GPUShader *DRW_shader_debug_print_display_get();
GPUShader *DRW_shader_debug_draw_display_get();
GPUShader *DRW_shader_draw_visibility_compute_get();
GPUShader *DRW_shader_draw_view_finalize_get();

View File

@@ -335,37 +335,6 @@ BLI_STATIC_ASSERT_ALIGN(DispatchCommand, 16)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debug print
* \{ */
/* Take the header (DrawCommand) into account. */
#define DRW_DEBUG_PRINT_MAX (8 * 1024) - 4
/** \note Cannot be more than 255 (because of column encoding). */
#define DRW_DEBUG_PRINT_WORD_WRAP_COLUMN 120u
/* The debug print buffer is laid-out as the following struct.
* But we use plain array in shader code instead because of driver issues. */
struct DRWDebugPrintBuffer {
DrawCommand command;
/** Each character is encoded as 3 `uchar` with char_index, row and column position. */
uint char_array[DRW_DEBUG_PRINT_MAX];
};
BLI_STATIC_ASSERT_ALIGN(DRWDebugPrintBuffer, 16)
/* Use number of char as vertex count. Equivalent to `DRWDebugPrintBuffer.command.v_count`. */
#define drw_debug_print_cursor drw_debug_print_buf[0]
/* Reuse first instance as row index as we don't use instancing. Equivalent to
* `DRWDebugPrintBuffer.command.i_first`. */
#define drw_debug_print_row_shared drw_debug_print_buf[3]
/**
* Offset to the first data. Equal to: `sizeof(DrawCommand) / sizeof(uint)`.
* This is needed because we bind the whole buffer as a `uint` array.
*/
#define drw_debug_print_offset 8
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debug draw shapes
* \{ */
@@ -400,7 +369,7 @@ struct DRWDebugDrawBuffer {
DrawCommand command;
DRWDebugVert verts[DRW_DEBUG_DRAW_VERT_MAX];
};
BLI_STATIC_ASSERT_ALIGN(DRWDebugPrintBuffer, 16)
BLI_STATIC_ASSERT_ALIGN(DRWDebugDrawBuffer, 16)
/* Equivalent to `DRWDebugDrawBuffer.command.v_count`. */
#define drw_debug_draw_v_count drw_debug_verts_buf[0].pos0

View File

@@ -1,425 +0,0 @@
/* SPDX-FileCopyrightText: 2022-2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
/**
* Debug print implementation for shaders.
*
* `drw_print()`:
* Log variable or strings inside the viewport.
* Using a unique non string argument will print the variable name with it.
* Concatenate by using multiple arguments. i.e: `drw_print("Looped ", n, "times.")`.
* `drw_print_no_endl()`:
* Same as `print()` but does not finish the line.
* `drw_print_value()`:
* Display only the value of a variable. Does not finish the line.
* `drw_print_value_hex()`:
* Display only the hex representation of a variable. Does not finish the line.
* `drw_print_value_binary()`: Display only the binary representation of a
* variable. Does not finish the line.
*
* IMPORTANT: As it is now, it is not yet thread safe. Only print from one thread. You can use the
* IS_DEBUG_MOUSE_FRAGMENT macro in fragment shader to filter using mouse position or
* IS_FIRST_INVOCATION in compute shaders.
*
* NOTE: Floating point representation might not be very precise (see drw_print_value(float)).
*
* IMPORTANT: Multiple drawcalls can write to the buffer in sequence (if they are from different
* shgroups). However, we add barriers to support this case and it might change the application
* behavior. Uncomment DISABLE_DEBUG_SHADER_drw_print_BARRIER to remove the barriers if that
* happens. But then you are limited to a single invocation output.
*
* IMPORTANT: All of these are copied to the CPU debug libraries (draw_debug.cc).
* They need to be kept in sync to write the same data.
*/
#ifdef DRW_DEBUG_PRINT
/** Global switch option when you want to silence all prints from all shaders at once. */
bool drw_debug_print_enable = true;
/* Set drw_print_col to max value so we will start by creating a new line and get the correct
* threadsafe row. */
uint drw_print_col = DRW_DEBUG_PRINT_WORD_WRAP_COLUMN;
uint drw_print_row = 0u;
void drw_print_newline()
{
if (!drw_debug_print_enable) {
return;
}
drw_print_col = 0u;
drw_print_row = atomicAdd(drw_debug_print_row_shared, 1u) + 1u;
}
void drw_print_string_start(uint len)
{
if (!drw_debug_print_enable) {
return;
}
/* Break before word. */
if (drw_print_col + len > DRW_DEBUG_PRINT_WORD_WRAP_COLUMN) {
drw_print_newline();
}
}
void drw_print_char4(uint data)
{
if (!drw_debug_print_enable) {
return;
}
/* Convert into char stream. */
for (; data != 0u; data >>= 8u) {
uint char1 = data & 0xFFu;
/* Check for null terminator. */
if (char1 == 0x00) {
break;
}
uint cursor = atomicAdd(drw_debug_print_cursor, 1u);
cursor += drw_debug_print_offset;
if (cursor < DRW_DEBUG_PRINT_MAX) {
/* For future usage. (i.e: Color) */
uint flags = 0u;
uint col = drw_print_col++;
uint drw_print_header = (flags << 24u) | (drw_print_row << 16u) | (col << 8u);
drw_debug_print_buf[cursor] = drw_print_header | char1;
/* Break word. */
if (drw_print_col > DRW_DEBUG_PRINT_WORD_WRAP_COLUMN) {
drw_print_newline();
}
}
}
}
/**
* NOTE(fclem): Strange behavior emerge when trying to increment the digit
* counter inside the append function. It looks like the compiler does not see
* it is referenced as an index for char4 and thus do not capture the right
* reference. I do not know if this is undefined behavior. As a matter of
* precaution, we implement all the append function separately. This behavior
* was observed on both MESA & AMDGPU-PRO.
*/
/* Using ascii char code. Expect char1 to be less or equal to 0xFF. Appends chars to the right. */
void drw_print_append_char(uint char_1, inout uint char_4)
{
char_4 = (char_4 << 8u) | char_1;
}
void drw_print_append_digit(uint digit, inout uint char_4)
{
const uint char_A = 0x41u;
const uint char_0 = 0x30u;
bool is_hexadecimal = digit > 9u;
char_4 = (char_4 << 8u) | (is_hexadecimal ? (char_A + digit - 10u) : (char_0 + digit));
}
void drw_print_append_space(inout uint char_4)
{
char_4 = (char_4 << 8u) | 0x20u;
}
void drw_print_value_binary(uint value)
{
drw_print_no_endl("0b");
drw_print_string_start(10u * 4u);
uint digits[10] = uint_array(0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u, 0u);
uint digit = 0u;
for (uint i = 0u; i < 32u; i++) {
drw_print_append_digit(((value >> i) & 1u), digits[digit / 4u]);
digit++;
if ((i % 4u) == 3u) {
drw_print_append_space(digits[digit / 4u]);
digit++;
}
}
/* Numbers are written from right to left. So we need to reverse the order. */
for (int j = 9; j >= 0; j--) {
drw_print_char4(digits[j]);
}
}
void drw_print_value_binary(int value)
{
drw_print_value_binary(uint(value));
}
void drw_print_value_binary(float value)
{
drw_print_value_binary(floatBitsToUint(value));
}
void drw_print_value_uint(uint value, const bool hex, bool is_negative, const bool is_unsigned)
{
drw_print_string_start(3u * 4u);
const uint blank_value = hex ? 0x30303030u : 0x20202020u;
const uint prefix = hex ? 0x78302020u : 0x20202020u;
uint digits[3] = uint_array(blank_value, blank_value, prefix);
const uint base = hex ? 16u : 10u;
uint digit = 0u;
/* Add `u` suffix. */
if (is_unsigned) {
drw_print_append_char('u', digits[digit / 4u]);
digit++;
}
/* Number's digits. */
for (; value != 0u || digit == uint(is_unsigned); value /= base) {
drw_print_append_digit(value % base, digits[digit / 4u]);
digit++;
}
/* Add negative sign. */
if (is_negative) {
drw_print_append_char('-', digits[digit / 4u]);
digit++;
}
/* Need to pad to uint alignment because we are issuing chars in "reverse". */
for (uint i = digit % 4u; i < 4u && i > 0u; i++) {
drw_print_append_space(digits[digit / 4u]);
digit++;
}
/* Numbers are written from right to left. So we need to reverse the order. */
for (int j = 2; j >= 0; j--) {
drw_print_char4(digits[j]);
}
}
void drw_print_value_hex(uint value)
{
drw_print_value_uint(value, true, false, false);
}
void drw_print_value_hex(int value)
{
drw_print_value_uint(uint(value), true, false, false);
}
void drw_print_value_hex(float value)
{
drw_print_value_uint(floatBitsToUint(value), true, false, false);
}
void drw_print_value(uint value)
{
drw_print_value_uint(value, false, false, true);
}
void drw_print_value(int value)
{
drw_print_value_uint(uint(abs(value)), false, (value < 0), false);
}
# ifndef GPU_METAL
void drw_print_value(bool value)
{
if (value) {
drw_print_no_endl("true ");
}
else {
drw_print_no_endl("false");
}
}
# endif
/* NOTE(@fclem): This is home-brew and might not be 100% accurate (accuracy has
* not been tested and might dependent on compiler implementation). If unsure,
* use drw_print_value_hex and transcribe the value manually with another tool. */
void drw_print_value(float val)
{
/* We pad the string to match normal float values length. */
if (isnan(val)) {
drw_print_no_endl(" NaN");
return;
}
if (isinf(val)) {
if (sign(val) < 0.0) {
drw_print_no_endl(" -Inf");
}
else {
drw_print_no_endl(" Inf");
}
return;
}
/* Adjusted for significant digits (6) with sign (1), decimal separator (1)
* and exponent (4). */
const float significant_digits = 6.0;
drw_print_string_start(3u * 4u);
uint digits[3] = uint_array(0x20202020u, 0x20202020u, 0x20202020u);
float exponent = floor(log(abs(val)) / log(10.0));
bool display_exponent = exponent >= (significant_digits) ||
exponent <= (-significant_digits + 1.0);
if (exponent < -1.0) {
/* FIXME(fclem): Display of values with exponent from -1 to -5 is broken. Force scientific
* notation in these cases. */
display_exponent = true;
}
float int_significant_digits = min(exponent + 1.0, significant_digits);
float dec_significant_digits = max(0.0, significant_digits - int_significant_digits);
/* Power to get to the rounding point. */
float rounding_power = dec_significant_digits;
if (val == 0.0 || isinf(exponent)) {
display_exponent = false;
int_significant_digits = dec_significant_digits = 1.0;
}
/* Remap to keep significant numbers count. */
if (display_exponent) {
int_significant_digits = 1.0;
dec_significant_digits = significant_digits - int_significant_digits;
rounding_power = -exponent + dec_significant_digits;
}
/* Round at the last significant digit. */
val = round(val * pow(10.0, rounding_power));
/* Get back to final exponent. */
val *= pow(10.0, -dec_significant_digits);
float int_part;
float dec_part = modf(val, int_part);
dec_part *= pow(10.0, dec_significant_digits);
const uint base = 10u;
uint digit = 0u;
/* Exponent */
uint value = uint(abs(exponent));
if (display_exponent) {
for (int i = 0; value != 0u || i == 0; i++, value /= base) {
drw_print_append_digit(value % base, digits[digit / 4u]);
digit++;
}
/* Exponent sign. */
uint sign_char = (exponent < 0.0) ? '-' : '+';
drw_print_append_char(sign_char, digits[digit / 4u]);
digit++;
/* Exponent `e` suffix. */
drw_print_append_char(0x65u, digits[digit / 4u]);
digit++;
}
/* Decimal part. */
value = uint(abs(dec_part));
# if 0 /* We don't do that because it makes unstable values really hard to read. */
/* Trim trailing zeros. */
while ((value % base) == 0u) {
value /= base;
if (value == 0u) {
break;
}
}
# endif
if (value != 0u) {
for (int i = 0; value != 0u || i == 0; i++, value /= base) {
drw_print_append_digit(value % base, digits[digit / 4u]);
digit++;
}
/* Point separator. */
drw_print_append_char('.', digits[digit / 4u]);
digit++;
}
/* Integer part. */
value = uint(abs(int_part));
for (int i = 0; value != 0u || i == 0; i++, value /= base) {
drw_print_append_digit(value % base, digits[digit / 4u]);
digit++;
}
/* Negative sign. */
if (val < 0.0) {
drw_print_append_char('-', digits[digit / 4u]);
digit++;
}
/* Need to pad to uint alignment because we are issuing chars in "reverse". */
for (uint i = digit % 4u; i < 4u && i > 0u; i++) {
drw_print_append_space(digits[digit / 4u]);
digit++;
}
/* Numbers are written from right to left. So we need to reverse the order. */
for (int j = 2; j >= 0; j--) {
drw_print_char4(digits[j]);
}
}
void drw_print_value(vec2 value)
{
drw_print_no_endl("vec2(", value[0], ", ", value[1], ")");
}
void drw_print_value(vec3 value)
{
drw_print_no_endl("vec3(", value[0], ", ", value[1], ", ", value[2], ")");
}
void drw_print_value(vec4 value)
{
drw_print_no_endl("vec4(", value[0], ", ", value[1], ", ", value[2], ", ", value[3], ")");
}
void drw_print_value(ivec2 value)
{
drw_print_no_endl("ivec2(", value[0], ", ", value[1], ")");
}
void drw_print_value(ivec3 value)
{
drw_print_no_endl("ivec3(", value[0], ", ", value[1], ", ", value[2], ")");
}
void drw_print_value(ivec4 value)
{
drw_print_no_endl("ivec4(", value[0], ", ", value[1], ", ", value[2], ", ", value[3], ")");
}
void drw_print_value(uvec2 value)
{
drw_print_no_endl("uvec2(", value[0], ", ", value[1], ")");
}
void drw_print_value(uvec3 value)
{
drw_print_no_endl("uvec3(", value[0], ", ", value[1], ", ", value[2], ")");
}
void drw_print_value(uvec4 value)
{
drw_print_no_endl("uvec4(", value[0], ", ", value[1], ", ", value[2], ", ", value[3], ")");
}
void drw_print_value(bvec2 value)
{
drw_print_no_endl("bvec2(", value[0], ", ", value[1], ")");
}
void drw_print_value(bvec3 value)
{
drw_print_no_endl("bvec3(", value[0], ", ", value[1], ", ", value[2], ")");
}
void drw_print_value(bvec4 value)
{
drw_print_no_endl("bvec4(", value[0], ", ", value[1], ", ", value[2], ", ", value[3], ")");
}
void drw_print_value(mat4 value)
{
drw_print("mat4x4(");
drw_print(" ", value[0]);
drw_print(" ", value[1]);
drw_print(" ", value[2]);
drw_print(" ", value[3]);
drw_print(")");
}
void drw_print_value(mat3 value)
{
drw_print("mat3x3(");
drw_print(" ", value[0]);
drw_print(" ", value[1]);
drw_print(" ", value[2]);
drw_print(")");
}
#endif

View File

@@ -381,7 +381,7 @@ vec4 gpencil_vertex(vec4 viewport_size,
out float out_hardness)
{
return gpencil_vertex(viewport_size,
0u,
gpMaterialFlag(0u),
vec2(1.0, 0.0),
out_P,
out_N,

View File

@@ -387,7 +387,7 @@ vec4 gpencil_vertex(vec4 viewport_size,
out float out_hardness)
{
return gpencil_vertex(viewport_size,
0u,
gpMaterialFlag(0u),
vec2(1.0, 0.0),
out_P,
out_N,

View File

@@ -5,35 +5,6 @@
#include "draw_defines.hh"
#include "gpu_shader_create_info.hh"
/* -------------------------------------------------------------------- */
/** \name Debug print
*
* Allows print() function to have logging support inside shaders.
* \{ */
GPU_SHADER_CREATE_INFO(draw_debug_print)
DEFINE("DRW_DEBUG_PRINT")
TYPEDEF_SOURCE("draw_shader_shared.hh")
STORAGE_BUF(DRW_DEBUG_PRINT_SLOT, READ_WRITE, uint, drw_debug_print_buf[])
GPU_SHADER_CREATE_END()
GPU_SHADER_INTERFACE_INFO(draw_debug_print_display_iface)
FLAT(UINT, char_index)
GPU_SHADER_INTERFACE_END()
GPU_SHADER_CREATE_INFO(draw_debug_print_display)
DO_STATIC_COMPILATION()
TYPEDEF_SOURCE("draw_shader_shared.hh")
STORAGE_BUF(DRW_DEBUG_PRINT_SLOT, READ, uint, drw_debug_print_buf[])
VERTEX_OUT(draw_debug_print_display_iface)
FRAGMENT_OUT(0, VEC4, out_color)
PUSH_CONSTANT(VEC2, viewport_size)
VERTEX_SOURCE("draw_debug_print_display_vert.glsl")
FRAGMENT_SOURCE("draw_debug_print_display_frag.glsl")
GPU_SHADER_CREATE_END()
/** \} */
/* -------------------------------------------------------------------- */
/** \name Debug draw shapes
*

View File

@@ -1,137 +0,0 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Display characters using an ascii table.
*/
bool char_intersect(uvec2 bitmap_position)
{
/* Using 8x8 = 64bits = uvec2. */
uvec2 ascii_bitmap[96] = uint2_array(uvec2(0x00000000u, 0x00000000u),
uvec2(0x18001800u, 0x183c3c18u),
uvec2(0x00000000u, 0x36360000u),
uvec2(0x7f363600u, 0x36367f36u),
uvec2(0x301f0c00u, 0x0c3e031eu),
uvec2(0x0c666300u, 0x00633318u),
uvec2(0x3b336e00u, 0x1c361c6eu),
uvec2(0x00000000u, 0x06060300u),
uvec2(0x060c1800u, 0x180c0606u),
uvec2(0x180c0600u, 0x060c1818u),
uvec2(0x3c660000u, 0x00663cffu),
uvec2(0x0c0c0000u, 0x000c0c3fu),
uvec2(0x000c0c06u, 0x00000000u),
uvec2(0x00000000u, 0x0000003fu),
uvec2(0x000c0c00u, 0x00000000u),
uvec2(0x06030100u, 0x6030180cu),
uvec2(0x6f673e00u, 0x3e63737bu),
uvec2(0x0c0c3f00u, 0x0c0e0c0cu),
uvec2(0x06333f00u, 0x1e33301cu),
uvec2(0x30331e00u, 0x1e33301cu),
uvec2(0x7f307800u, 0x383c3633u),
uvec2(0x30331e00u, 0x3f031f30u),
uvec2(0x33331e00u, 0x1c06031fu),
uvec2(0x0c0c0c00u, 0x3f333018u),
uvec2(0x33331e00u, 0x1e33331eu),
uvec2(0x30180e00u, 0x1e33333eu),
uvec2(0x000c0c00u, 0x000c0c00u),
uvec2(0x000c0c06u, 0x000c0c00u),
uvec2(0x060c1800u, 0x180c0603u),
uvec2(0x003f0000u, 0x00003f00u),
uvec2(0x180c0600u, 0x060c1830u),
uvec2(0x0c000c00u, 0x1e333018u),
uvec2(0x7b031e00u, 0x3e637b7bu),
uvec2(0x3f333300u, 0x0c1e3333u),
uvec2(0x66663f00u, 0x3f66663eu),
uvec2(0x03663c00u, 0x3c660303u),
uvec2(0x66361f00u, 0x1f366666u),
uvec2(0x16467f00u, 0x7f46161eu),
uvec2(0x16060f00u, 0x7f46161eu),
uvec2(0x73667c00u, 0x3c660303u),
uvec2(0x33333300u, 0x3333333fu),
uvec2(0x0c0c1e00u, 0x1e0c0c0cu),
uvec2(0x33331e00u, 0x78303030u),
uvec2(0x36666700u, 0x6766361eu),
uvec2(0x46667f00u, 0x0f060606u),
uvec2(0x6b636300u, 0x63777f7fu),
uvec2(0x73636300u, 0x63676f7bu),
uvec2(0x63361c00u, 0x1c366363u),
uvec2(0x06060f00u, 0x3f66663eu),
uvec2(0x3b1e3800u, 0x1e333333u),
uvec2(0x36666700u, 0x3f66663eu),
uvec2(0x38331e00u, 0x1e33070eu),
uvec2(0x0c0c1e00u, 0x3f2d0c0cu),
uvec2(0x33333f00u, 0x33333333u),
uvec2(0x331e0c00u, 0x33333333u),
uvec2(0x7f776300u, 0x6363636bu),
uvec2(0x1c366300u, 0x6363361cu),
uvec2(0x0c0c1e00u, 0x3333331eu),
uvec2(0x4c667f00u, 0x7f633118u),
uvec2(0x06061e00u, 0x1e060606u),
uvec2(0x30604000u, 0x03060c18u),
uvec2(0x18181e00u, 0x1e181818u),
uvec2(0x00000000u, 0x081c3663u),
uvec2(0x000000ffu, 0x00000000u),
uvec2(0x00000000u, 0x0c0c1800u),
uvec2(0x3e336e00u, 0x00001e30u),
uvec2(0x66663b00u, 0x0706063eu),
uvec2(0x03331e00u, 0x00001e33u),
uvec2(0x33336e00u, 0x3830303eu),
uvec2(0x3f031e00u, 0x00001e33u),
uvec2(0x06060f00u, 0x1c36060fu),
uvec2(0x333e301fu, 0x00006e33u),
uvec2(0x66666700u, 0x0706366eu),
uvec2(0x0c0c1e00u, 0x0c000e0cu),
uvec2(0x3033331eu, 0x30003030u),
uvec2(0x1e366700u, 0x07066636u),
uvec2(0x0c0c1e00u, 0x0e0c0c0cu),
uvec2(0x7f6b6300u, 0x0000337fu),
uvec2(0x33333300u, 0x00001f33u),
uvec2(0x33331e00u, 0x00001e33u),
uvec2(0x663e060fu, 0x00003b66u),
uvec2(0x333e3078u, 0x00006e33u),
uvec2(0x66060f00u, 0x00003b6eu),
uvec2(0x1e301f00u, 0x00003e03u),
uvec2(0x0c2c1800u, 0x080c3e0cu),
uvec2(0x33336e00u, 0x00003333u),
uvec2(0x331e0c00u, 0x00003333u),
uvec2(0x7f7f3600u, 0x0000636bu),
uvec2(0x1c366300u, 0x00006336u),
uvec2(0x333e301fu, 0x00003333u),
uvec2(0x0c263f00u, 0x00003f19u),
uvec2(0x0c0c3800u, 0x380c0c07u),
uvec2(0x18181800u, 0x18181800u),
uvec2(0x0c0c0700u, 0x070c0c38u),
uvec2(0x00000000u, 0x6e3b0000u),
uvec2(0x00000000u, 0x00000000u));
if (any(lessThan(bitmap_position, uvec2(0))) || any(greaterThanEqual(bitmap_position, uvec2(8))))
{
return false;
}
uint char_bits = ascii_bitmap[char_index % 96u][bitmap_position.y >> 2u & 1u];
char_bits = (char_bits >> ((bitmap_position.y & 3u) * 8u + bitmap_position.x));
return (char_bits & 1u) != 0u;
}
void main()
{
uvec2 bitmap_position = uvec2(gl_PointCoord.xy * 8.0);
#ifndef GPU_METAL /* Metal has different gl_PointCoord.y. */
/* Point coord start from top left corner. But layout is from bottom to top. */
bitmap_position.y = 7 - bitmap_position.y;
#endif
if (char_intersect(bitmap_position)) {
out_color = vec4(1);
}
else if (char_intersect(bitmap_position + uvec2(0, 1))) {
/* Shadow */
out_color = vec4(0, 0, 0, 1);
}
else {
/* Transparent Background for ease of read. */
out_color = vec4(0, 0, 0, 0.2);
}
}

View File

@@ -1,29 +0,0 @@
/* SPDX-FileCopyrightText: 2022 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Display characters using an ascii table. Outputs one point per character.
*/
void main()
{
/* Skip first 4 chars containing header data. */
uint char_data = drw_debug_print_buf[gl_VertexID + 8];
char_index = (char_data & 0xFFu) - 0x20u;
/* Discard invalid chars. */
if (char_index >= 96u) {
gl_Position = vec4(-1);
gl_PointSize = 0.0;
return;
}
uint row = (char_data >> 16u) & 0xFFu;
uint col = (char_data >> 8u) & 0xFFu;
float char_size = 16.0;
/* Change anchor point to the top left. */
vec2 pos_on_screen = char_size * vec2(col, row) + char_size * 4;
gl_Position = vec4((pos_on_screen / viewport_size) * vec2(2.0, -2.0) - vec2(1.0, -1.0), 0, 1);
gl_PointSize = char_size;
}

View File

@@ -42,24 +42,46 @@ int main(int argc, char **argv)
buffer << input_file.rdbuf();
int error = 0;
size_t line_index = 0;
auto report_error =
[&](const std::string &src_line, const std::smatch &match, const char *err_msg) {
size_t err_line = line_index;
size_t err_char = match.position();
auto count_lines = [](const std::string &str) {
size_t lines = 0;
for (char c : str) {
if (c == '\n') {
lines++;
}
}
return lines;
};
std::cerr << input_file_name;
std::cerr << ':' << std::to_string(err_line) << ':' << std::to_string(err_char);
std::cerr << ": error: " << err_msg << std::endl;
std::cerr << src_line << std::endl;
std::cerr << std::string(err_char, ' ') << '^' << std::endl;
auto get_line = [&](size_t line) {
std::string src = buffer.str();
size_t start = 0, end;
for (; line > 1; line--) {
start = src.find('\n', start + 1);
}
end = src.find('\n', start + 1);
return src.substr(start + 1, end - (start + 1));
};
error++;
};
auto report_error = [&](const std::smatch &match, const char *err_msg) {
size_t remaining_lines = count_lines(match.suffix());
size_t total_lines = count_lines(buffer.str());
size_t err_line = 1 + total_lines - remaining_lines;
size_t err_char = (match.prefix().str().size() - 1) - match.prefix().str().rfind('\n');
std::cerr << input_file_name;
std::cerr << ':' << std::to_string(err_line) << ':' << std::to_string(err_char);
std::cerr << ": error: " << err_msg << std::endl;
std::cerr << get_line(err_line) << std::endl;
std::cerr << std::string(err_char, ' ') << '^' << std::endl;
error++;
};
const bool is_info = std::string(output_file_name).find("info.hh") != std::string::npos;
const bool is_glsl = std::string(output_file_name).find(".glsl") != std::string::npos;
const bool is_shared = std::string(output_file_name).find("shared.h") != std::string::npos;
if (is_info) {
std::cerr << "File " << output_file_name
@@ -69,7 +91,8 @@ int main(int argc, char **argv)
blender::gpu::shader::Preprocessor processor;
output_file << processor.process(buffer.str(), true, is_glsl, is_glsl, report_error);
output_file << processor.process(
buffer.str(), input_file_name, true, is_glsl, is_glsl, is_shared, report_error);
input_file.close();
output_file.close();

View File

@@ -8,10 +8,12 @@
#pragma once
#include <algorithm>
#include <cstdint>
#include <functional>
#include <regex>
#include <sstream>
#include <string>
#include <unordered_set>
#include <vector>
namespace blender::gpu::shader {
@@ -24,44 +26,101 @@ namespace blender::gpu::shader {
* shaders source.
*/
class Preprocessor {
using uint = unsigned int;
using uint64_t = std::uint64_t;
using report_callback = std::function<void(const std::smatch &, const char *)>;
struct SharedVar {
std::string type;
std::string name;
std::string array;
};
std::vector<SharedVar> shared_vars_;
std::stringstream output_;
std::vector<SharedVar> shared_vars_;
std::unordered_set<std::string> static_strings_;
std::unordered_set<std::string> gpu_builtins_;
/* Note: Could be a set, but for now the order matters. */
std::vector<std::string> dependencies_;
std::stringstream gpu_functions_;
public:
/* Compile-time hashing function which converts string to a 64bit hash. */
constexpr static uint64_t hash(const char *name)
{
uint64_t hash = 2166136261u;
while (*name) {
hash = hash * 16777619u;
hash = hash ^ *name;
++name;
}
return hash;
}
static uint64_t hash(const std::string &name)
{
return hash(name.c_str());
}
/* Takes a whole source file and output processed source. */
template<typename ReportErrorF>
std::string process(std::string str,
bool /*do_linting*/,
bool /*do_string_mutation*/,
bool /*do_include_mutation*/,
const ReportErrorF &report_error)
const std::string &filename,
bool do_linting,
bool do_string_mutation,
bool do_include_mutation,
bool do_small_type_linting,
report_callback report_error)
{
str = remove_comments(str, report_error);
threadgroup_variable_parsing(str);
matrix_constructor_linting(str, report_error);
array_constructor_linting(str, report_error);
str = preprocessor_directive_mutation(str);
threadgroup_variables_parsing(str);
parse_builtins(str);
if (true) {
parse_library_functions(str);
}
if (do_include_mutation) {
preprocessor_parse(str);
str = preprocessor_directive_mutation(str);
}
if (do_string_mutation) {
static_strings_parsing(str);
str = static_strings_mutation(str);
str = printf_processing(str, report_error);
str = remove_quotes(str);
}
if (do_linting) {
matrix_constructor_linting(str, report_error);
array_constructor_linting(str, report_error);
}
if (do_small_type_linting) {
small_type_linting(str, report_error);
}
str = enum_macro_injection(str);
str = argument_decorator_macro_injection(str);
str = array_constructor_macro_injection(str);
return str + suffix();
return line_directive_prefix(filename) + str + threadgroup_variables_suffix() +
"//__blender_metadata_sta\n" + gpu_functions_.str() + static_strings_suffix() +
gpu_builtins_suffix(filename) + dependency_suffix() + "//__blender_metadata_end\n";
}
/* Variant use for python shaders. */
std::string process(const std::string &str)
{
auto no_err_report = [](std::string, std::smatch, const char *) {};
return process(str, false, false, false, no_err_report);
auto no_err_report = [](std::smatch, const char *) {};
return process(str, "", false, false, false, false, no_err_report);
}
private:
using regex_callback = std::function<void(const std::smatch &)>;
/* Helper to make the code more readable in parsing functions. */
void regex_global_search(const std::string &str,
const std::regex &regex,
regex_callback callback)
{
using namespace std;
string::const_iterator it = str.begin();
for (smatch match; regex_search(it, str.end(), match, regex); it = match.suffix().first) {
callback(match);
}
}
template<typename ReportErrorF>
std::string remove_comments(const std::string &str, const ReportErrorF &report_error)
{
@@ -83,7 +142,7 @@ class Preprocessor {
if (end == std::string::npos) {
/* TODO(fclem): Add line / char position to report. */
report_error(str, std::smatch(), "Malformed multi-line comment.");
report_error(std::smatch(), "Malformed multi-line comment.");
return out_str;
}
}
@@ -102,7 +161,7 @@ class Preprocessor {
if (end == std::string::npos) {
/* TODO(fclem): Add line / char position to report. */
report_error(str, std::smatch(), "Malformed single line comment, missing newline.");
report_error(std::smatch(), "Malformed single line comment, missing newline.");
return out_str;
}
}
@@ -111,19 +170,256 @@ class Preprocessor {
return std::regex_replace(out_str, regex, "\n");
}
std::string preprocessor_directive_mutation(const std::string &str)
std::string remove_quotes(const std::string &str)
{
/* Example: `#include "deps.glsl"` > `//include "deps.glsl"` */
std::regex regex("#\\s*(include|pragma once)");
return std::regex_replace(str, regex, "//$1");
return std::regex_replace(str, std::regex(R"(["'])"), " ");
}
void threadgroup_variable_parsing(std::string str)
void preprocessor_parse(const std::string &str)
{
/* Parse include directive before removing them. */
std::regex regex(R"(#\s*include\s*\"(\w+\.\w+)\")");
regex_global_search(str, regex, [&](const std::smatch &match) {
std::string dependency_name = match[1].str();
if (dependency_name == "gpu_glsl_cpp_stubs.hh") {
/* Skip GLSL-C++ stubs. They are only for IDE linting. */
return;
}
dependencies_.emplace_back(dependency_name);
});
}
std::string preprocessor_directive_mutation(const std::string &str)
{
/* Remove unsupported directives.` */
std::regex regex(R"(#\s*(?:include|pragma once)[^\n]*)");
return std::regex_replace(str, regex, "");
}
std::string dependency_suffix()
{
if (dependencies_.empty()) {
return "";
}
std::stringstream suffix;
for (const std::string &filename : dependencies_) {
suffix << "// " << std::to_string(hash("dependency")) << " " << filename << "\n";
}
return suffix.str();
}
void threadgroup_variables_parsing(const std::string &str)
{
std::regex regex("shared\\s+(\\w+)\\s+(\\w+)([^;]*);");
for (std::smatch match; std::regex_search(str, match, regex); str = match.suffix()) {
regex_global_search(str, regex, [&](const std::smatch &match) {
shared_vars_.push_back({match[1].str(), match[2].str(), match[3].str()});
});
}
void parse_library_functions(const std::string &str)
{
std::regex regex_func(R"(void\s+(\w+)\s*\(([^)]+\))\s*\{)");
regex_global_search(str, regex_func, [&](const std::smatch &match) {
std::string name = match[1].str();
std::string args = match[2].str();
gpu_functions_ << "// " << hash("function") << " " << name;
std::regex regex_arg(R"((?:(const|in|out|inout)\s)?(\w+)\s([\w\[\]]+)(?:,|\)))");
regex_global_search(args, regex_arg, [&](const std::smatch &arg) {
std::string qualifier = arg[1].str();
std::string type = arg[2].str();
if (qualifier.empty() || qualifier == "const") {
qualifier = "in";
}
gpu_functions_ << ' ' << hash(qualifier) << ' ' << hash(type);
});
gpu_functions_ << "\n";
});
}
void parse_builtins(const std::string &str)
{
/* TODO: This can trigger false positive caused by disabled #if blocks. */
std::regex regex(
"("
"gl_FragCoord|"
"gl_FrontFacing|"
"gl_GlobalInvocationID|"
"gl_InstanceID|"
"gl_LocalInvocationID|"
"gl_LocalInvocationIndex|"
"gl_NumWorkGroup|"
"gl_PointCoord|"
"gl_PointSize|"
"gl_PrimitiveID|"
"gl_VertexID|"
"gl_WorkGroupID|"
"gl_WorkGroupSize|"
"drw_debug_|"
"printf"
")");
regex_global_search(
str, regex, [&](const std::smatch &match) { gpu_builtins_.insert(match[0].str()); });
}
std::string gpu_builtins_suffix(const std::string &filename)
{
if (gpu_builtins_.empty()) {
return "";
}
const bool skip_drw_debug = filename.find("common_debug_draw_lib.glsl") != std::string::npos ||
filename.find("draw_debug_draw_display_vert.glsl") !=
std::string::npos;
std::stringstream suffix;
for (const std::string &str_var : gpu_builtins_) {
if (str_var == "drw_debug_" && skip_drw_debug) {
continue;
}
suffix << "// " << hash("builtin") << " " << hash(str_var) << "\n";
}
return suffix.str();
}
template<typename ReportErrorF>
std::string printf_processing(const std::string &str, const ReportErrorF &report_error)
{
std::string out_str = str;
{
/* Example: `printf(2, b, f(c, d));` > `printf(2@ b@ f(c@ d))$` */
size_t start, end = 0;
while ((start = out_str.find("printf(", end)) != std::string::npos) {
end = out_str.find(';', start);
if (end == std::string::npos) {
break;
}
out_str[end] = '$';
int bracket_depth = 0;
int arg_len = 0;
for (size_t i = start; i < end; ++i) {
if (out_str[i] == '(') {
bracket_depth++;
}
else if (out_str[i] == ')') {
bracket_depth--;
}
else if (bracket_depth == 1 && out_str[i] == ',') {
out_str[i] = '@';
arg_len++;
}
}
if (arg_len > 99) {
report_error(std::smatch(), "Too many parameters in printf. Max is 99.");
break;
}
/* Encode number of arg in the `ntf` of `printf`. */
out_str[start + sizeof("printf") - 4] = '$';
out_str[start + sizeof("printf") - 3] = ((arg_len / 10) > 0) ? ('0' + arg_len / 10) : '$';
out_str[start + sizeof("printf") - 2] = '0' + arg_len % 10;
}
if (end == 0) {
/* No printf in source. */
return str;
}
}
/* Example: `pri$$1(2@ b)$` > `{int c_ = print_header(1, 2); c_ = print_data(c_, b); }` */
{
std::regex regex(R"(pri\$\$?(\d{1,2})\()");
out_str = std::regex_replace(out_str, regex, "{int c_ = print_header($1, ");
}
{
std::regex regex("\\@");
out_str = std::regex_replace(out_str, regex, "); c_ = print_data(c_,");
}
{
std::regex regex("\\$");
out_str = std::regex_replace(out_str, regex, "; }");
}
return out_str;
}
void static_strings_parsing(const std::string &str)
{
/* Matches any character inside a pair of unescaped quote. */
std::regex regex(R"("(?:[^"])*")");
regex_global_search(
str, regex, [&](const std::smatch &match) { static_strings_.insert(match[0].str()); });
}
std::string static_strings_mutation(std::string str)
{
/* Replaces all matches by the respective string hash. */
for (const std::string &str_var : static_strings_) {
std::regex escape_regex(R"([\\\.\^\$\+\(\)\[\]\{\}\|\?\*])");
std::string str_regex = std::regex_replace(str_var, escape_regex, "\\$&");
uint64_t hash_64 = hash(str_var);
uint32_t hash_32 = uint32_t(hash_64 ^ (hash_64 >> 32));
std::regex regex(str_regex);
str = std::regex_replace(str, regex, std::to_string(hash_32) + 'u');
}
return str;
}
std::string static_strings_suffix()
{
if (static_strings_.empty()) {
return "";
}
std::stringstream suffix;
for (const std::string &str_var : static_strings_) {
suffix << "// " << hash("string") << " " << hash(str_var) << " " << str_var << "\n";
}
return suffix.str();
}
std::string enum_macro_injection(std::string str)
{
/**
* Transform C,C++ enum declaration into GLSL compatible defines and constants:
*
* \code{.cpp}
* enum eMyEnum : uint32_t {
* ENUM_1 = 0u,
* ENUM_2 = 1u,
* ENUM_3 = 2u,
* };
* \endcode
*
* becomes
*
* \code{.glsl}
* _enum_decl(_eMyEnum)
* ENUM_1 = 0u,
* ENUM_2 = 1u,
* ENUM_3 = 2u, _enum_end
* #define eMyEnum _enum_type(_eMyEnum)
* \endcode
*
* It is made like so to avoid messing with error lines, allowing to point at the exact
* location inside the source file.
*
* IMPORTANT: This has some requirements:
* - Enums needs to have underlying types set to uint32_t to make them usable in UBO and SSBO.
* - All values needs to be specified using constant literals to avoid compiler differences.
* - All values needs to have the 'u' suffix to avoid GLSL compiler errors.
*/
{
/* Replaces all matches by the respective string hash. */
std::regex regex(R"(enum\s+((\w+)\s*(?:\:\s*\w+\s*)?)\{(\n[^}]+)\n\};)");
str = std::regex_replace(str,
regex,
"_enum_decl(_$1)$3 _enum_end\n"
"#define $2 _enum_type(_$2)");
}
{
/* Remove trailing comma if any. */
std::regex regex(R"(,(\s*_enum_end))");
str = std::regex_replace(str, regex, "$1");
}
return str;
}
std::string argument_decorator_macro_injection(const std::string &str)
@@ -142,32 +438,41 @@ class Preprocessor {
/* TODO(fclem): Too many false positive and false negative to be applied to python shaders. */
template<typename ReportErrorF>
void matrix_constructor_linting(std::string str, const ReportErrorF &report_error)
void matrix_constructor_linting(const std::string &str, const ReportErrorF &report_error)
{
/* Example: `mat4(other_mat)`. */
std::regex regex("\\s+(mat(\\d|\\dx\\d)|float\\dx\\d)\\([^,\\s\\d]+\\)");
for (std::smatch match; std::regex_search(str, match, regex); str = match.suffix()) {
regex_global_search(str, regex, [&](const std::smatch &match) {
/* This only catches some invalid usage. For the rest, the CI will catch them. */
const char *msg =
"Matrix constructor is not cross API compatible. "
"Use to_floatNxM to reshape the matrix or use other constructors instead.";
report_error(str, match, msg);
}
report_error(match, msg);
});
}
template<typename ReportErrorF>
void array_constructor_linting(std::string str, const ReportErrorF &report_error)
void array_constructor_linting(const std::string &str, const ReportErrorF &report_error)
{
std::regex regex("=\\s*(\\w+)\\s*\\[[^\\]]*\\]\\s*\\(");
for (std::smatch match; std::regex_search(str, match, regex); str = match.suffix()) {
regex_global_search(str, regex, [&](const std::smatch &match) {
/* This only catches some invalid usage. For the rest, the CI will catch them. */
const char *msg =
"Array constructor is not cross API compatible. Use type_array instead of type[].";
report_error(str, match, msg);
}
report_error(match, msg);
});
}
std::string suffix()
template<typename ReportErrorF>
void small_type_linting(const std::string &str, const ReportErrorF &report_error)
{
std::regex regex(R"(\su?(char|short|half)(2|3|4)?\s)");
regex_global_search(str, regex, [&](const std::smatch &match) {
report_error(match, "Small types are forbidden in shader interfaces.");
});
}
std::string threadgroup_variables_suffix()
{
if (shared_vars_.empty()) {
return "";
@@ -187,6 +492,7 @@ class Preprocessor {
* Each part of the codegen is stored inside macros so that we don't have to do string
* replacement at runtime.
*/
suffix << "\n";
/* Arguments of the wrapper class constructor. */
suffix << "#undef MSL_SHARED_VARS_ARGS\n";
/* References assignment inside wrapper class constructor. */
@@ -251,9 +557,78 @@ class Preprocessor {
suffix << "#define MSL_SHARED_VARS_ASSIGN " << assign.str() << "\n";
suffix << "#define MSL_SHARED_VARS_DECLARE " << declare.str() << "\n";
suffix << "#define MSL_SHARED_VARS_PASS (" << pass.str() << ")\n";
suffix << "\n";
return suffix.str();
}
std::string line_directive_prefix(const std::string &filepath)
{
std::stringstream suffix;
suffix << "#line 1 ";
#ifdef __APPLE__
/* For now, only Metal supports filname in line directive.
* There is no way to know the actual backend, so we assume Apple uses Metal. */
/* TODO(fclem): We could make it work using a macro to choose between the filename and the hash
* at runtime. i.e.: `FILENAME_MACRO(12546546541, 'filename.glsl')` This should work for both
* MSL and GLSL. */
std::string filename = std::regex_replace(filepath, std::regex(R"((?:.*)\/(.*))"), "$1");
if (!filename.empty()) {
suffix << "\"" << filename << "\"";
}
#else
uint64_t hash_value = hash(filepath);
/* Fold the value so it fits the GLSL spec. */
hash_value = (hash_value ^ (hash_value >> 32)) & (~uint64_t(0) >> 33);
suffix << std::to_string(uint64_t(hash_value));
#endif
suffix << "\n";
return suffix.str();
}
};
/* Enum values of metadata that the preprocessor can append at the end of a source file.
* Eventually, remove the need for these and output the metadata inside header files. */
namespace metadata {
enum Builtin : uint64_t {
FragCoord = Preprocessor::hash("gl_FragCoord"),
FrontFacing = Preprocessor::hash("gl_FrontFacing"),
GlobalInvocationID = Preprocessor::hash("gl_GlobalInvocationID"),
InstanceID = Preprocessor::hash("gl_InstanceID"),
LocalInvocationID = Preprocessor::hash("gl_LocalInvocationID"),
LocalInvocationIndex = Preprocessor::hash("gl_LocalInvocationIndex"),
NumWorkGroup = Preprocessor::hash("gl_NumWorkGroup"),
PointCoord = Preprocessor::hash("gl_PointCoord"),
PointSize = Preprocessor::hash("gl_PointSize"),
PrimitiveID = Preprocessor::hash("gl_PrimitiveID"),
VertexID = Preprocessor::hash("gl_VertexID"),
WorkGroupID = Preprocessor::hash("gl_WorkGroupID"),
WorkGroupSize = Preprocessor::hash("gl_WorkGroupSize"),
drw_debug = Preprocessor::hash("drw_debug_"),
printf = Preprocessor::hash("printf"),
};
enum Qualifier : uint64_t {
in = Preprocessor::hash("in"),
out = Preprocessor::hash("out"),
inout = Preprocessor::hash("inout"),
};
enum Type : uint64_t {
vec1 = Preprocessor::hash("float"),
vec2 = Preprocessor::hash("vec2"),
vec3 = Preprocessor::hash("vec3"),
vec4 = Preprocessor::hash("vec4"),
mat3 = Preprocessor::hash("mat3"),
mat4 = Preprocessor::hash("mat4"),
sampler1DArray = Preprocessor::hash("sampler1DArray"),
sampler2DArray = Preprocessor::hash("sampler2DArray"),
sampler2D = Preprocessor::hash("sampler2D"),
sampler3D = Preprocessor::hash("sampler3D"),
Closure = Preprocessor::hash("Closure"),
};
} // namespace metadata
} // namespace blender::gpu::shader

View File

@@ -580,9 +580,6 @@ void gpu_shader_create_info_init()
if ((info->builtins_ & BuiltinBits::USE_DEBUG_DRAW) == BuiltinBits::USE_DEBUG_DRAW) {
info->additional_info("draw_debug_draw");
}
if ((info->builtins_ & BuiltinBits::USE_DEBUG_PRINT) == BuiltinBits::USE_DEBUG_PRINT) {
info->additional_info("draw_debug_print");
}
#endif
}

View File

@@ -421,9 +421,8 @@ enum class BuiltinBits {
/* Not a builtin but a flag we use to tag shaders that use the debug features. */
USE_PRINTF = (1 << 28),
USE_DEBUG_DRAW = (1 << 29),
USE_DEBUG_PRINT = (1 << 30),
};
ENUM_OPERATORS(BuiltinBits, BuiltinBits::USE_DEBUG_PRINT);
ENUM_OPERATORS(BuiltinBits, BuiltinBits::USE_DEBUG_DRAW);
/**
* Follow convention described in:

View File

@@ -14,6 +14,7 @@
#include <iostream>
#include <regex>
#include <sstream>
#include <string>
#include "BLI_ghash.h"
#include "BLI_map.hh"
@@ -24,6 +25,8 @@
#include "gpu_shader_create_info.hh"
#include "gpu_shader_dependency_private.hh"
#include "../glsl_preprocess/glsl_preprocess.hh"
#include "GPU_context.hh"
extern "C" {
@@ -47,10 +50,130 @@ struct GPUSource {
StringRefNull fullpath;
StringRefNull filename;
StringRefNull source;
Vector<StringRef> dependencies_names;
Vector<GPUSource *> dependencies;
bool dependencies_init = false;
shader::BuiltinBits builtins = shader::BuiltinBits::NONE;
std::string processed_source;
shader::BuiltinBits parse_builtin_bit(StringRef builtin)
{
using namespace blender::gpu::shader;
using namespace blender::gpu::shader::metadata;
switch (Builtin(std::stoull(builtin))) {
case Builtin::FragCoord:
return BuiltinBits::FRAG_COORD;
case Builtin::FrontFacing:
return BuiltinBits::FRONT_FACING;
case Builtin::GlobalInvocationID:
return BuiltinBits::GLOBAL_INVOCATION_ID;
case Builtin::InstanceID:
return BuiltinBits::INSTANCE_ID;
case Builtin::LocalInvocationID:
return BuiltinBits::LOCAL_INVOCATION_ID;
case Builtin::LocalInvocationIndex:
return BuiltinBits::LOCAL_INVOCATION_INDEX;
case Builtin::NumWorkGroup:
return BuiltinBits::NUM_WORK_GROUP;
case Builtin::PointCoord:
return BuiltinBits::POINT_COORD;
case Builtin::PointSize:
return BuiltinBits::POINT_SIZE;
case Builtin::PrimitiveID:
return BuiltinBits::PRIMITIVE_ID;
case Builtin::VertexID:
return BuiltinBits::VERTEX_ID;
case Builtin::WorkGroupID:
return BuiltinBits::WORK_GROUP_ID;
case Builtin::WorkGroupSize:
return BuiltinBits::WORK_GROUP_SIZE;
case Builtin::drw_debug:
#ifndef NDEBUG
return BuiltinBits::USE_DEBUG_DRAW;
#else
return BuiltinBits::NONE;
#endif
case Builtin::printf:
#if GPU_SHADER_PRINTF_ENABLE
return BuiltinBits::USE_PRINTF;
#else
return BuiltinBits::NONE;
#endif
}
BLI_assert_unreachable();
return BuiltinBits::NONE;
}
GPUFunctionQual parse_qualifier(StringRef qualifier)
{
using namespace blender::gpu::shader;
switch (metadata::Qualifier(std::stoull(qualifier))) {
case metadata::Qualifier::in:
return FUNCTION_QUAL_IN;
case metadata::Qualifier::out:
return FUNCTION_QUAL_OUT;
case metadata::Qualifier::inout:
return FUNCTION_QUAL_INOUT;
}
BLI_assert_unreachable();
return FUNCTION_QUAL_IN;
}
eGPUType parse_type(StringRef type)
{
using namespace blender::gpu::shader;
switch (metadata::Type(std::stoull(type))) {
case metadata::Type::vec1:
return GPU_FLOAT;
case metadata::Type::vec2:
return GPU_VEC2;
case metadata::Type::vec3:
return GPU_VEC3;
case metadata::Type::vec4:
return GPU_VEC4;
case metadata::Type::mat3:
return GPU_MAT3;
case metadata::Type::mat4:
return GPU_MAT4;
case metadata::Type::sampler1DArray:
return GPU_TEX1D_ARRAY;
case metadata::Type::sampler2DArray:
return GPU_TEX2D_ARRAY;
case metadata::Type::sampler2D:
return GPU_TEX2D;
case metadata::Type::sampler3D:
return GPU_TEX3D;
case metadata::Type::Closure:
return GPU_CLOSURE;
}
BLI_assert_unreachable();
return GPU_NONE;
}
StringRef split_on(StringRef &data, char token)
{
/* Assume lines are terminated by `\n`. */
int64_t pos = data.find(token);
if (pos == StringRef::not_found) {
StringRef line = data;
data = data.substr(0, 0);
return line;
}
StringRef line = data.substr(0, pos);
data = data.substr(pos + 1);
return line;
}
StringRef pop_line(StringRef &data)
{
/* Assume lines are terminated by `\n`. */
return split_on(data, '\n');
}
StringRef pop_token(StringRef &data)
{
/* Assumes tokens are split by spaces. */
return split_on(data, ' ');
}
GPUSource(const char *path,
const char *file,
@@ -59,882 +182,62 @@ struct GPUSource {
GPUPrintFormatMap *g_formats)
: fullpath(path), filename(file), source(datatoc)
{
/* Scan for builtins. */
/* FIXME: This can trigger false positive caused by disabled #if blocks. */
/* TODO(fclem): Could be made faster by scanning once. */
if (source.find("gl_FragCoord", 0) != StringRef::not_found) {
builtins |= shader::BuiltinBits::FRAG_COORD;
}
if (source.find("gl_FrontFacing", 0) != StringRef::not_found) {
builtins |= shader::BuiltinBits::FRONT_FACING;
}
if (source.find("gl_GlobalInvocationID", 0) != StringRef::not_found) {
builtins |= shader::BuiltinBits::GLOBAL_INVOCATION_ID;
}
if (source.find("gl_InstanceID", 0) != StringRef::not_found) {
builtins |= shader::BuiltinBits::INSTANCE_ID;
}
if (source.find("gl_LocalInvocationID", 0) != StringRef::not_found) {
builtins |= shader::BuiltinBits::LOCAL_INVOCATION_ID;
}
if (source.find("gl_LocalInvocationIndex", 0) != StringRef::not_found) {
builtins |= shader::BuiltinBits::LOCAL_INVOCATION_INDEX;
}
if (source.find("gl_NumWorkGroup", 0) != StringRef::not_found) {
builtins |= shader::BuiltinBits::NUM_WORK_GROUP;
}
if (source.find("gl_PointCoord", 0) != StringRef::not_found) {
builtins |= shader::BuiltinBits::POINT_COORD;
}
if (source.find("gl_PointSize", 0) != StringRef::not_found) {
builtins |= shader::BuiltinBits::POINT_SIZE;
}
if (source.find("gl_PrimitiveID", 0) != StringRef::not_found) {
builtins |= shader::BuiltinBits::PRIMITIVE_ID;
}
if (source.find("gl_VertexID", 0) != StringRef::not_found) {
builtins |= shader::BuiltinBits::VERTEX_ID;
}
if (source.find("gl_WorkGroupID", 0) != StringRef::not_found) {
builtins |= shader::BuiltinBits::WORK_GROUP_ID;
}
if (source.find("gl_WorkGroupSize", 0) != StringRef::not_found) {
builtins |= shader::BuiltinBits::WORK_GROUP_SIZE;
}
/* TODO(fclem): We could do that at compile time. */
/* Limit to shared header files to avoid the temptation to use C++ syntax in .glsl files. */
if (filename.endswith(".h") || filename.endswith(".hh")) {
enum_preprocess();
quote_preprocess();
small_types_check();
}
else {
if (source.find("'") != StringRef::not_found) {
char_literals_preprocess();
}
#ifndef NDEBUG
if (source.find("drw_print") != StringRef::not_found) {
string_preprocess();
}
if ((source.find("drw_debug_") != StringRef::not_found) &&
/* Avoid this file as it is a false positive match (matches "drw_debug_print_buf"). */
filename != "draw_debug_print_display_vert.glsl" &&
/* Avoid these two files where it makes no sense to add the dependency. */
!ELEM(filename, "common_debug_draw_lib.glsl", "draw_debug_draw_display_vert.glsl"))
{
builtins |= shader::BuiltinBits::USE_DEBUG_DRAW;
}
#endif
#if GPU_SHADER_PRINTF_ENABLE
if (source.find("printf") != StringRef::not_found) {
printf_preprocess(g_formats);
builtins |= shader::BuiltinBits::USE_PRINTF;
}
#else
(void)g_formats;
#endif
check_no_quotes();
}
/* Extract metadata string. */
int64_t sta = source.rfind("//__blender_metadata_sta");
int64_t end = source.rfind("//__blender_metadata_end");
StringRef metadata = source.substr(sta, end - sta);
pop_line(metadata);
if (is_from_material_library()) {
material_functions_parse(g_functions);
/* Non-library files contains functions with unsupported argument types.
* Also Non-library files are not supposed to be referenced for GPU node-tree. */
const bool do_parse_function = is_from_material_library();
StringRef line;
while ((line = pop_line(metadata)).is_empty() == false) {
using namespace blender::gpu::shader;
/* Skip comment start. */
pop_token(line);
StringRef identifier = pop_token(line);
switch (uint64_t(std::stoull(identifier))) {
case Preprocessor::hash("function"):
if (do_parse_function) {
parse_function(line, g_functions);
}
break;
case Preprocessor::hash("string"):
parse_string(line, g_formats);
break;
case Preprocessor::hash("builtin"):
parse_builtin(line);
break;
case Preprocessor::hash("dependency"):
parse_dependency(line);
break;
default:
BLI_assert_unreachable();
break;
}
}
};
static bool is_in_comment(const StringRef &input, int64_t offset)
void parse_builtin(StringRef line)
{
return (input.rfind("/*", offset) > input.rfind("*/", offset)) ||
(input.rfind("//", offset) > input.rfind("\n", offset));
builtins |= parse_builtin_bit(pop_token(line));
}
template<bool check_whole_word = true, bool reversed = false, typename T>
static int64_t find_str(const StringRef &input, const T keyword, int64_t offset = 0)
void parse_dependency(StringRef line)
{
while (true) {
if constexpr (reversed) {
offset = input.rfind(keyword, offset);
}
else {
offset = input.find(keyword, offset);
}
if (offset > 0) {
if constexpr (check_whole_word) {
/* Fix false positive if something has "enum" as suffix. */
char previous_char = input[offset - 1];
if (!ELEM(previous_char, '\n', '\t', ' ', ':', '(', ',')) {
offset += (reversed) ? -1 : 1;
continue;
}
}
/* Fix case where the keyword is in a comment. */
if (is_in_comment(input, offset)) {
offset += (reversed) ? -1 : 1;
continue;
}
}
return offset;
}
dependencies_names.append(line);
}
#define find_keyword find_str<true, false>
#define rfind_keyword find_str<true, true>
#define find_token find_str<false, false>
#define rfind_token find_str<false, true>
void print_error(const StringRef &input, int64_t offset, const StringRef message)
void parse_string(StringRef line, GPUPrintFormatMap *format_map)
{
StringRef sub = input.substr(0, offset);
int64_t line_number = std::count(sub.begin(), sub.end(), '\n') + 1;
int64_t line_end = input.find("\n", offset);
int64_t line_start = input.rfind("\n", offset) + 1;
int64_t char_number = offset - line_start + 1;
/* TODO Use clog. */
std::cerr << fullpath << ":" << line_number << ":" << char_number;
std::cerr << " error: " << message << "\n";
std::cerr << std::setw(5) << line_number << " | "
<< input.substr(line_start, line_end - line_start) << "\n";
std::cerr << " | ";
for (int64_t i = 0; i < char_number - 1; i++) {
std::cerr << " ";
}
std::cerr << "^\n";
}
#define CHECK(test_value, str, ofs, msg) \
if ((test_value) == -1) { \
print_error(str, ofs, msg); \
continue; \
}
/**
* Some drivers completely forbid quote characters even in unused preprocessor directives.
* We fix the cases where we can't manually patch in `enum_preprocess()`.
* This check ensure none are present in non-patched sources. (see #97545)
*/
void check_no_quotes()
{
#ifndef NDEBUG
int64_t pos = -1;
do {
pos = source.find('"', pos + 1);
if (pos == -1) {
break;
}
if (!is_in_comment(source, pos)) {
print_error(source, pos, "Quote characters are forbidden in GLSL files");
}
} while (true);
#endif
}
/**
* Some drivers completely forbid string characters even in unused preprocessor directives.
* This fixes the cases we cannot manually patch: Shared headers #includes. (see #97545)
* TODO(fclem): This could be done during the datatoc step.
*/
void quote_preprocess()
{
if (source.find_first_of('"') == -1) {
return;
}
processed_source = source;
std::replace(processed_source.begin(), processed_source.end(), '"', ' ');
source = processed_source.c_str();
}
/**
* Assert not small types are present inside shader shared files.
*/
void small_types_check()
{
#ifndef NDEBUG
auto check_type = [&](StringRefNull type_str) {
int64_t cursor = -1;
while (true) {
cursor = find_keyword(source, type_str, cursor + 1);
if (cursor == -1) {
break;
}
print_error(source, cursor, "small types are forbidden in shader interfaces");
}
};
check_type("char ");
check_type("char2 ");
check_type("char3 ");
check_type("char4 ");
check_type("uchar ");
check_type("uchar2 ");
check_type("uchar3 ");
check_type("uchar4 ");
check_type("short ");
check_type("short2 ");
check_type("short3 ");
check_type("short4 ");
check_type("ushort ");
check_type("ushort2 ");
check_type("ushort3 ");
check_type("ushort4 ");
check_type("half ");
check_type("half2 ");
check_type("half3 ");
check_type("half4 ");
#endif
}
/**
* Transform C,C++ enum declaration into GLSL compatible defines and constants:
*
* \code{.cpp}
* enum eMyEnum : uint32_t {
* ENUM_1 = 0u,
* ENUM_2 = 1u,
* ENUM_3 = 2u,
* };
* \endcode
*
* or
*
* \code{.c}
* enum eMyEnum {
* ENUM_1 = 0u,
* ENUM_2 = 1u,
* ENUM_3 = 2u,
* };
* \endcode
*
* becomes
*
* \code{.glsl}
* #define eMyEnum uint
* const uint ENUM_1 = 0u, ENUM_2 = 1u, ENUM_3 = 2u;
* \endcode
*
* IMPORTANT: This has some requirements:
* - Enums needs to have underlying types specified to uint32_t to make them usable in UBO/SSBO.
* - All values needs to be specified using constant literals to avoid compiler differences.
* - All values needs to have the 'u' suffix to avoid GLSL compiler errors.
*/
void enum_preprocess()
{
const StringRefNull input = source;
std::string output;
int64_t cursor = -1;
int64_t last_pos = 0;
const bool is_cpp = filename.endswith(".hh");
/* Metal Shading language is based on C++ and supports C++-style enumerations.
* For these cases, we do not need to perform auto-replacement. */
if (is_cpp && GPU_backend_get_type() == GPU_BACKEND_METAL) {
return;
}
while (true) {
cursor = find_keyword(input, "enum ", cursor + 1);
if (cursor == -1) {
break;
}
/* Skip matches like `typedef enum myEnum myType;` */
if (cursor >= 8 && input.substr(cursor - 8, 8) == "typedef ") {
continue;
}
/* Output anything between 2 enums blocks. */
output += input.substr(last_pos, cursor - last_pos);
/* Extract enum type name. */
int64_t name_start = input.find(" ", cursor);
int64_t values_start = find_token(input, '{', cursor);
CHECK(values_start, input, cursor, "Malformed enum class. Expected \'{\' after typename.");
StringRef enum_name = input.substr(name_start, values_start - name_start);
if (is_cpp) {
int64_t name_end = find_token(enum_name, ":");
CHECK(name_end, input, name_start, "Expected \':\' after C++ enum name.");
int64_t underlying_type = find_keyword(enum_name, "uint32_t", name_end);
CHECK(underlying_type, input, name_start, "C++ enums needs uint32_t underlying type.");
enum_name = input.substr(name_start, name_end);
}
output += "#define " + enum_name + " uint\n";
/* Extract enum values. */
int64_t values_end = find_token(input, '}', values_start);
CHECK(values_end, input, cursor, "Malformed enum class. Expected \'}\' after values.");
/* Skip opening brackets. */
values_start += 1;
StringRef enum_values = input.substr(values_start, values_end - values_start);
/* Really poor check. Could be done better. */
int64_t token = find_token(enum_values, '{');
int64_t not_found = (token == -1) ? 0 : -1;
CHECK(not_found, input, values_start + token, "Unexpected \'{\' token inside enum values.");
/* Do not capture the comma after the last value (if present). */
int64_t last_equal = rfind_token(enum_values, '=', values_end);
int64_t last_comma = rfind_token(enum_values, ',', values_end);
if (last_comma > last_equal) {
enum_values = input.substr(values_start, last_comma);
}
output += "const uint " + enum_values;
int64_t semicolon_found = (input[values_end + 1] == ';') ? 0 : -1;
CHECK(semicolon_found, input, values_end + 1, "Expected \';\' after enum type declaration.");
/* Skip the curly bracket but not the semicolon. */
cursor = last_pos = values_end + 1;
}
/* If nothing has been changed, do not allocate processed_source. */
if (last_pos == 0) {
return;
}
if (last_pos != 0) {
output += input.substr(last_pos);
}
processed_source = output;
source = processed_source.c_str();
};
void material_functions_parse(GPUFunctionDictionnary *g_functions)
{
const StringRefNull input = source;
const char whitespace_chars[] = " \r\n\t";
auto function_parse = [&](const StringRef input,
int64_t &cursor,
StringRef &out_return_type,
StringRef &out_name,
StringRef &out_args) -> bool {
cursor = find_keyword(input, "void ", cursor + 1);
if (cursor == -1) {
return false;
}
int64_t arg_start = find_token(input, '(', cursor);
if (arg_start == -1) {
return false;
}
int64_t arg_end = find_token(input, ')', arg_start);
if (arg_end == -1) {
return false;
}
int64_t body_start = find_token(input, '{', arg_end);
int64_t next_semicolon = find_token(input, ';', arg_end);
if (body_start != -1 && next_semicolon != -1 && body_start > next_semicolon) {
/* Assert no prototypes but could also just skip them. */
BLI_assert_msg(false, "No prototypes allowed in node GLSL libraries.");
}
int64_t name_start = input.find_first_not_of(whitespace_chars, input.find(' ', cursor));
if (name_start == -1) {
return false;
}
int64_t name_end = input.find_last_not_of(whitespace_chars, arg_start);
if (name_end == -1) {
return false;
}
/* Only support void type for now. */
out_return_type = "void";
out_name = input.substr(name_start, name_end - name_start);
out_args = input.substr(arg_start + 1, arg_end - (arg_start + 1));
return true;
};
auto keyword_parse = [&](const StringRef str, int64_t &cursor) -> StringRef {
int64_t keyword_start = str.find_first_not_of(whitespace_chars, cursor);
if (keyword_start == -1) {
/* No keyword found. */
return str.substr(0, 0);
}
int64_t keyword_end = str.find_first_of(whitespace_chars, keyword_start);
if (keyword_end == -1) {
/* Last keyword. */
keyword_end = str.size();
}
cursor = keyword_end + 1;
return str.substr(keyword_start, keyword_end - keyword_start);
};
auto arg_parse = [&](const StringRef str,
int64_t &cursor,
StringRef &out_qualifier,
StringRef &out_type,
StringRef &out_name) -> bool {
int64_t arg_start = cursor + 1;
if (arg_start >= str.size()) {
return false;
}
cursor = find_token(str, ',', arg_start);
if (cursor == -1) {
/* Last argument. */
cursor = str.size();
}
const StringRef arg = str.substr(arg_start, cursor - arg_start);
int64_t keyword_cursor = 0;
out_qualifier = keyword_parse(arg, keyword_cursor);
out_type = keyword_parse(arg, keyword_cursor);
/* Skip qualifier prefix macro expanded by GLSL preprocessing (e.g. _out_sta). */
StringRef qualifier_prefix = keyword_parse(arg, keyword_cursor);
out_name = keyword_parse(arg, keyword_cursor);
if (out_qualifier == "const") {
out_name = qualifier_prefix;
}
if (out_name.is_empty()) {
/* No qualifier case. */
out_name = out_type;
out_type = out_qualifier;
out_qualifier = arg.substr(0, 0);
}
return true;
};
int64_t cursor = -1;
StringRef func_return_type, func_name, func_args;
while (function_parse(input, cursor, func_return_type, func_name, func_args)) {
/* Main functions needn't be handled because they are the entry point of the shader. */
if (func_name == "main") {
continue;
}
GPUFunction *func = MEM_new<GPUFunction>(__func__);
func_name.copy(func->name, sizeof(func->name));
func->source = reinterpret_cast<void *>(this);
bool insert = g_functions->add(func->name, func);
/* NOTE: We allow overloading non void function, but only if the function comes from the
* same file. Otherwise the dependency system breaks. */
if (!insert) {
GPUSource *other_source = reinterpret_cast<GPUSource *>(
g_functions->lookup(func_name)->source);
if (other_source != this) {
print_error(input,
source.find(func_name),
"Function redefinition or overload in two different files ...");
print_error(
input, other_source->source.find(func_name), "... previous definition was here");
}
else {
/* Non-void function overload. */
MEM_delete(func);
}
continue;
}
if (func_return_type != "void") {
continue;
}
func->totparam = 0;
int64_t args_cursor = -1;
StringRef arg_qualifier, arg_type, arg_name;
while (arg_parse(func_args, args_cursor, arg_qualifier, arg_type, arg_name)) {
if (func->totparam >= ARRAY_SIZE(func->paramtype)) {
print_error(input, source.find(func_name), "Too much parameter in function");
break;
}
auto parse_qualifier = [](StringRef qualifier) -> GPUFunctionQual {
if (qualifier == "out") {
return FUNCTION_QUAL_OUT;
}
if (qualifier == "inout") {
return FUNCTION_QUAL_INOUT;
}
return FUNCTION_QUAL_IN;
};
auto parse_type = [](StringRef type) -> eGPUType {
if (type == "float") {
return GPU_FLOAT;
}
if (type == "vec2") {
return GPU_VEC2;
}
if (type == "vec3") {
return GPU_VEC3;
}
if (type == "vec4") {
return GPU_VEC4;
}
if (type == "mat3") {
return GPU_MAT3;
}
if (type == "mat4") {
return GPU_MAT4;
}
if (type == "sampler1DArray") {
return GPU_TEX1D_ARRAY;
}
if (type == "sampler2DArray") {
return GPU_TEX2D_ARRAY;
}
if (type == "sampler2D") {
return GPU_TEX2D;
}
if (type == "sampler3D") {
return GPU_TEX3D;
}
if (type == "Closure") {
return GPU_CLOSURE;
}
return GPU_NONE;
};
func->paramqual[func->totparam] = parse_qualifier(arg_qualifier);
func->paramtype[func->totparam] = parse_type(arg_type);
if (func->paramtype[func->totparam] == GPU_NONE) {
std::string err = "Unknown parameter type \"" + arg_type + "\"";
int64_t err_ofs = source.find(func_name);
err_ofs = find_keyword(source, arg_name, err_ofs);
err_ofs = rfind_keyword(source, arg_type, err_ofs);
print_error(input, err_ofs, err);
}
func->totparam++;
}
}
}
void char_literals_preprocess()
{
const StringRefNull input = source;
std::stringstream output;
int64_t cursor = -1;
int64_t last_pos = 0;
while (true) {
cursor = find_token(input, '\'', cursor + 1);
if (cursor == -1) {
break;
}
/* Output anything between 2 print statement. */
output << input.substr(last_pos, cursor - last_pos);
/* Extract string. */
int64_t char_start = cursor + 1;
int64_t char_end = find_token(input, '\'', char_start);
CHECK(char_end, input, cursor, "Malformed char literal. Missing ending `'`.");
StringRef input_char = input.substr(char_start, char_end - char_start);
if (input_char.is_empty()) {
CHECK(-1, input, cursor, "Malformed char literal. Empty character constant");
}
uint8_t char_value = input_char[0];
if (input_char[0] == '\\') {
if (input_char[1] == 'n') {
char_value = '\n';
}
else {
CHECK(-1, input, cursor, "Unsupported escaped character");
}
}
else {
if (input_char.size() > 1) {
CHECK(-1, input, cursor, "Malformed char literal. Multi-character character constant");
}
}
char hex[8];
SNPRINTF(hex, "0x%.2Xu", char_value);
output << hex;
cursor = last_pos = char_end + 1;
}
/* If nothing has been changed, do not allocate processed_source. */
if (last_pos == 0) {
return;
}
if (last_pos != 0) {
output << input.substr(last_pos);
}
processed_source = output.str();
source = processed_source.c_str();
}
/* Replace print(string) by equivalent drw_print_char4() sequence. */
void string_preprocess()
{
const StringRefNull input = source;
std::stringstream output;
int64_t cursor = -1;
int64_t last_pos = 0;
while (true) {
cursor = find_keyword(input, "drw_print", cursor + 1);
if (cursor == -1) {
break;
}
bool do_endl = false;
StringRef func = input.substr(cursor);
if (func.startswith("drw_print(")) {
do_endl = true;
}
else if (func.startswith("drw_print_no_endl(")) {
do_endl = false;
}
else {
continue;
}
/* Output anything between 2 print statement. */
output << input.substr(last_pos, cursor - last_pos);
/* Extract string. */
int64_t str_start = input.find('(', cursor) + 1;
int64_t semicolon = find_token(input, ';', str_start + 1);
CHECK(semicolon, input, cursor, "Malformed print(). Missing `;` .");
int64_t str_end = rfind_token(input, ')', semicolon);
if (str_end < str_start) {
CHECK(-1, input, cursor, "Malformed print(). Missing closing `)` .");
}
std::stringstream sub_output;
StringRef input_args = input.substr(str_start, str_end - str_start);
auto print_string = [&](std::string str) -> int {
size_t len_before_pad = str.length();
/* Pad string to uint size. */
while (str.length() % 4 != 0) {
str += " ";
}
/* Keep everything in one line to not mess with the shader logs. */
sub_output << "/* " << str << "*/";
sub_output << "drw_print_string_start(" << len_before_pad << ");";
for (size_t i = 0; i < len_before_pad; i += 4) {
uint8_t chars[4] = {*(reinterpret_cast<const uint8_t *>(str.c_str()) + i + 0),
*(reinterpret_cast<const uint8_t *>(str.c_str()) + i + 1),
*(reinterpret_cast<const uint8_t *>(str.c_str()) + i + 2),
*(reinterpret_cast<const uint8_t *>(str.c_str()) + i + 3)};
if (i + 4 > len_before_pad) {
chars[len_before_pad - i] = '\0';
}
char uint_hex[12];
SNPRINTF(uint_hex, "0x%.2X%.2X%.2X%.2Xu", chars[3], chars[2], chars[1], chars[0]);
sub_output << "drw_print_char4(" << StringRefNull(uint_hex) << ");";
}
return 0;
};
std::string func_args = input_args;
/* Workaround to support function call inside prints. We replace commas by a non control
* character `$` in order to use simpler regex later. */
bool string_scope = false;
int func_scope = 0;
for (char &c : func_args) {
if (c == '"') {
string_scope = !string_scope;
}
else if (!string_scope) {
if (c == '(') {
func_scope++;
}
else if (c == ')') {
func_scope--;
}
else if (c == ',' && func_scope != 0) {
c = '$';
}
}
}
const bool print_as_variable = (input_args[0] != '"') && find_token(input_args, ',') == -1;
if (print_as_variable) {
/* Variable or expression debugging. */
std::string arg = input_args;
/* Pad align most values. */
while (arg.length() % 4 != 0) {
arg += " ";
}
print_string(arg);
print_string("= ");
sub_output << "drw_print_value(" << input_args << ");";
}
else {
const std::regex arg_regex(
/* String args. */
"[\\s]*\"([^\r\n\t\f\v\"]*)\""
/* OR. */
"|"
/* value args. */
"([^,]+)");
std::smatch args_match;
std::string::const_iterator args_search_start(func_args.cbegin());
while (std::regex_search(args_search_start, func_args.cend(), args_match, arg_regex)) {
args_search_start = args_match.suffix().first;
std::string arg_string = args_match[1].str();
std::string arg_val = args_match[2].str();
if (arg_string.empty()) {
for (char &c : arg_val) {
if (c == '$') {
c = ',';
}
}
sub_output << "drw_print_value(" << arg_val << ");";
}
else {
print_string(arg_string);
}
}
}
if (do_endl) {
sub_output << "drw_print_newline();";
}
output << sub_output.str();
cursor = last_pos = str_end + 1;
}
/* If nothing has been changed, do not allocate processed_source. */
if (last_pos == 0) {
return;
}
if (filename != "common_debug_print_lib.glsl") {
builtins |= shader::BuiltinBits::USE_DEBUG_PRINT;
}
if (last_pos != 0) {
output << input.substr(last_pos);
}
processed_source = output.str();
source = processed_source.c_str();
}
/**
* Preprocess printf statement for correct shader code generation.
* `printf(format, data1, data2)`
* gets replaced by
* `print_data(print_data(print_header(format_hash, 2), data1), data2)`.
*/
void printf_preprocess(GPUPrintFormatMap *format_map)
{
const StringRefNull input = source;
std::stringstream output;
int64_t cursor = -1;
int64_t last_pos = 0;
while (true) {
cursor = find_keyword(input, "printf(", cursor + 1);
if (cursor == -1) {
break;
}
/* Output anything between 2 print statement. */
output << input.substr(last_pos, cursor - last_pos);
/* Extract string. */
int64_t str_start = input.find('(', cursor) + 1;
int64_t semicolon = find_token(input, ';', str_start + 1);
CHECK(semicolon, input, cursor, "Malformed printf(). Missing `;` .");
int64_t str_end = rfind_token(input, ')', semicolon);
if (str_end < str_start) {
CHECK(-1, input, cursor, "Malformed printf(). Missing closing `)` .");
}
StringRef input_args = input.substr(str_start, str_end - str_start);
std::string func_args = input_args;
/* Workaround to support function call inside prints. We replace commas by a non control
* character `$` in order to use simpler regex later.
* Modify `"func %d,\n", func(a, b)` into `"func %d,\n", func(a$ b)` */
bool string_scope = false;
int func_scope = 0;
for (char &c : func_args) {
if (c == '"') {
string_scope = !string_scope;
}
else if (!string_scope) {
if (c == '(') {
func_scope++;
}
else if (c == ')') {
func_scope--;
}
else if (c == ',' && func_scope != 0) {
c = '$';
}
}
}
std::string format;
Vector<std::string> arguments;
{
const std::regex arg_regex(
/* String args. */
"[\\s]*\"([^\r\n\t\f\v\"]*)\""
/* OR. */
"|"
/* value args. */
"([^,]+)");
std::smatch args_match;
std::string::const_iterator args_search_start(func_args.cbegin());
while (std::regex_search(args_search_start, func_args.cend(), args_match, arg_regex)) {
args_search_start = args_match.suffix().first;
std::string arg_string = args_match[1].str();
std::string arg_val = args_match[2].str();
if (!arg_string.empty()) {
if (!format.empty()) {
CHECK(-1, input, cursor, "Format string is not the only string arg.");
}
if (!arguments.is_empty()) {
CHECK(-1, input, cursor, "Format string is not first argument.");
}
format = arg_string;
}
else {
for (char &c : arg_val) {
/* Mutate back functions arguments.*/
if (c == '$') {
c = ',';
}
}
arguments.append(arg_val);
}
}
}
if (format.empty()) {
CHECK(-1, input, cursor, "No format string found.");
return;
}
int format_arg_count = std::count(format.begin(), format.end(), '%');
if (format_arg_count > arguments.size()) {
CHECK(-1, input, cursor, "printf call has not enough arguments.");
return;
}
if (format_arg_count < arguments.size()) {
CHECK(-1, input, cursor, "printf call has too many arguments.");
return;
}
uint64_t format_hash_64 = hash_string(format);
uint32_t format_hash = uint32_t((format_hash_64 >> 32) ^ format_hash_64);
/* TODO(fclem): Move this to gpu log. */
auto add_format = [&](uint32_t format_hash, std::string format) {
if (format_map->contains(format_hash)) {
if (format_map->lookup(format_hash).format_str != format) {
CHECK(-1, input, cursor, "printf format hash collision.");
print_error(format, 0, "printf format hash collision.");
}
else {
/* The format map already have the same format. */
@@ -983,33 +286,76 @@ struct GPUSource {
format_map->add(format_hash, fmt);
}
};
std::string sub_output = "print_header(" + std::to_string(format_hash) + "u, " +
std::to_string(format_arg_count) + "u)";
for (std::string &arg : arguments) {
sub_output = "print_data(" + sub_output + ", " + arg + ")";
}
output << sub_output << ";\n";
cursor = last_pos = str_end + 1;
}
/* If nothing has been changed, do not allocate processed_source. */
if (last_pos == 0) {
return;
}
if (last_pos != 0) {
output << input.substr(last_pos);
}
processed_source = output.str();
source = processed_source.c_str();
StringRef hash = pop_token(line);
StringRef string = line;
add_format(uint32_t(std::stol(hash)), string);
}
#undef find_keyword
#undef rfind_keyword
#undef find_token
#undef rfind_token
void parse_function(StringRef line, GPUFunctionDictionnary *g_functions)
{
StringRef name = pop_token(line);
GPUFunction *func = MEM_new<GPUFunction>(__func__);
name.copy(func->name, sizeof(func->name));
func->source = reinterpret_cast<void *>(this);
func->totparam = 0;
while (1) {
StringRef arg_qual = pop_token(line);
StringRef arg_type = pop_token(line);
if (arg_qual.is_empty()) {
break;
}
if (func->totparam >= ARRAY_SIZE(func->paramtype)) {
print_error(source, source.find(name), "Too many parameters in function");
break;
}
func->paramqual[func->totparam] = parse_qualifier(arg_qual);
func->paramtype[func->totparam] = parse_type(arg_type);
func->totparam++;
}
bool insert = g_functions->add(func->name, func);
/* NOTE: We allow overloading non void function, but only if the function comes from the
* same file. Otherwise the dependency system breaks. */
if (!insert) {
GPUSource *other_source = reinterpret_cast<GPUSource *>(g_functions->lookup(name)->source);
if (other_source != this) {
const char *msg = "Function redefinition or overload in two different files ...";
print_error(source, source.find(name), msg);
print_error(other_source->source,
other_source->source.find(name),
"... previous definition was here");
}
else {
/* Non-void function overload. */
MEM_delete(func);
}
}
}
void print_error(const StringRef &input, int64_t offset, const StringRef message)
{
StringRef sub = input.substr(0, offset);
int64_t line_number = std::count(sub.begin(), sub.end(), '\n') + 1;
int64_t line_end = input.find("\n", offset);
int64_t line_start = input.rfind("\n", offset) + 1;
int64_t char_number = offset - line_start + 1;
/* TODO Use clog. */
std::cerr << fullpath << ":" << line_number << ":" << char_number;
std::cerr << " error: " << message << "\n";
std::cerr << std::setw(5) << line_number << " | "
<< input.substr(line_start, line_end - line_start) << "\n";
std::cerr << " | ";
for (int64_t i = 0; i < char_number - 1; i++) {
std::cerr << " ";
}
std::cerr << "^\n";
}
/* Return 1 one error. */
int init_dependencies(const GPUSourceDictionnary &dict,
@@ -1019,7 +365,6 @@ struct GPUSource {
return 0;
}
this->dependencies_init = true;
int64_t pos = -1;
using namespace shader;
/* Auto dependency injection for debug capabilities. */
@@ -1029,36 +374,12 @@ struct GPUSource {
if ((builtins & BuiltinBits::USE_DEBUG_DRAW) == BuiltinBits::USE_DEBUG_DRAW) {
dependencies.append_non_duplicates(dict.lookup("common_debug_draw_lib.glsl"));
}
if ((builtins & BuiltinBits::USE_DEBUG_PRINT) == BuiltinBits::USE_DEBUG_PRINT) {
dependencies.append_non_duplicates(dict.lookup("common_debug_print_lib.glsl"));
}
while (true) {
GPUSource *dependency_source = nullptr;
{
/* Include directive has been mangled on purpose. See `glsl_preprocess.hh`. */
pos = source.find("\n//include \"", pos + 1);
if (pos == -1) {
return 0;
}
int64_t start = source.find('"', pos) + 1;
int64_t end = source.find('"', start + 1);
if (end == -1) {
print_error(source, start, "Malformed include: Missing \" token");
return 1;
}
StringRef dependency_name = source.substr(start, end - start);
if (dependency_name == "gpu_glsl_cpp_stubs.hh") {
/* Skip GLSL-C++ stubs. They are only for IDE linting. */
continue;
}
dependency_source = dict.lookup_default(dependency_name, nullptr);
if (dependency_source == nullptr) {
print_error(source, start, "Dependency not found");
return 1;
}
for (auto dependency_name : dependencies_names) {
GPUSource *dependency_source = dict.lookup_default(dependency_name, nullptr);
if (dependency_source == nullptr) {
print_error(source, 0, "Dependency not found");
return 1;
}
/* Recursive. */
@@ -1072,8 +393,7 @@ struct GPUSource {
}
dependencies.append_non_duplicates(dependency_source);
}
/* Precedes an eternal loop (quiet CLANG's `unreachable-code` warning). */
BLI_assert_unreachable();
dependencies_names.clear();
return 0;
}

View File

@@ -125,9 +125,6 @@ void Shader::print_log(Span<const char *> sources,
if (log_item.cursor.row == -1) {
found_line_id = false;
}
else if (log_item.source_base_row && log_item.cursor.source > 0) {
log_item.cursor.row += sources_end_line[log_item.cursor.source - 1];
}
const char *src_line = sources_combined;
@@ -146,8 +143,7 @@ void Shader::print_log(Span<const char *> sources,
{
const char *src_line_end;
found_line_id = false;
/* error_line is 1 based in this case. */
int src_line_index = 1;
int src_line_index = 0;
while ((src_line_end = strchr(src_line, '\n'))) {
if (src_line_index >= log_item.cursor.row) {
found_line_id = true;

View File

@@ -232,7 +232,6 @@ struct LogCursor {
struct GPULogItem {
LogCursor cursor;
bool source_base_row = false;
Severity severity = Severity::Unknown;
};

View File

@@ -218,18 +218,8 @@ void MTLShader::fragment_shader_from_glsl(MutableSpan<const char *> sources)
std::stringstream ss;
int i;
for (i = 0; i < sources.size(); i++) {
/* Output preprocessor directive to improve shader log. */
StringRefNull name = shader::gpu_shader_dependency_get_filename_from_source_string(sources[i]);
if (name.is_empty()) {
ss << "#line 1 \"generated_code_" << i << "\"\n";
}
else {
ss << "#line 1 \"" << name << "\"\n";
}
ss << sources[i] << '\n';
}
ss << "#line 1 \"msl_wrapper_code\"\n";
shd_builder_->glsl_fragment_source_ = ss.str();
}
@@ -245,14 +235,6 @@ void MTLShader::compute_shader_from_glsl(MutableSpan<const char *> sources)
/* Consolidate GLSL compute sources. */
std::stringstream ss;
for (int i = 0; i < sources.size(); i++) {
/* Output preprocessor directive to improve shader log. */
StringRefNull name = shader::gpu_shader_dependency_get_filename_from_source_string(sources[i]);
if (name.is_empty()) {
ss << "#line 1 \"generated_code_" << i << "\"\n";
}
else {
ss << "#line 1 \"" << name << "\"\n";
}
ss << sources[i] << std::endl;
}
shd_builder_->glsl_compute_source_ = ss.str();

View File

@@ -787,8 +787,8 @@ bool MTLShader::generate_msl_from_glsl(const shader::ShaderCreateInfo *info)
/* Setup `stringstream` for populating generated MSL shader vertex/frag shaders. */
std::stringstream ss_vertex;
std::stringstream ss_fragment;
ss_vertex << "#line 1 \"msl_wrapper_code\"\n";
ss_fragment << "#line 1 \"msl_wrapper_code\"\n";
ss_vertex << "#line " STRINGIFY(__LINE__) " \"" __FILE__ "\"" << std::endl;
ss_fragment << "#line " STRINGIFY(__LINE__) " \"" __FILE__ "\"" << std::endl;
if (bool(info->builtins_ & BuiltinBits::TEXTURE_ATOMIC) &&
MTLBackend::get_capabilities().supports_texture_atomics)
@@ -982,6 +982,7 @@ bool MTLShader::generate_msl_from_glsl(const shader::ShaderCreateInfo *info)
/* Inject main GLSL source into output stream. */
ss_vertex << shd_builder_->glsl_vertex_source_ << std::endl;
ss_vertex << "#line " STRINGIFY(__LINE__) " \"" __FILE__ "\"" << std::endl;
/* Generate VertexOut and TransformFeedbackOutput structs. */
ss_vertex << msl_iface.generate_msl_vertex_out_struct(ShaderStage::VERTEX);
@@ -1108,6 +1109,7 @@ bool MTLShader::generate_msl_from_glsl(const shader::ShaderCreateInfo *info)
/* Inject Main GLSL Fragment Source into output stream. */
ss_fragment << shd_builder_->glsl_fragment_source_ << std::endl;
ss_fragment << "#line " STRINGIFY(__LINE__) " \"" __FILE__ "\"" << std::endl;
/* Class Closing Bracket to end shader global scope. */
ss_fragment << "};" << std::endl;
@@ -1246,7 +1248,7 @@ bool MTLShader::generate_msl_from_glsl_compute(const shader::ShaderCreateInfo *i
/** Generate Compute shader stage. **/
std::stringstream ss_compute;
ss_compute << "#line 1 \"msl_wrapper_code\"\n";
ss_compute << "#line " STRINGIFY(__LINE__) " \"" __FILE__ "\"" << std::endl;
ss_compute << "#define GPU_ARB_shader_draw_parameters 1\n";
if (bool(info->builtins_ & BuiltinBits::TEXTURE_ATOMIC) &&
@@ -1332,6 +1334,7 @@ bool MTLShader::generate_msl_from_glsl_compute(const shader::ShaderCreateInfo *i
/* Inject main GLSL source into output stream. */
ss_compute << shd_builder_->glsl_compute_source_ << std::endl;
ss_compute << "#line " STRINGIFY(__LINE__) " \"" __FILE__ "\"" << std::endl;
/* Compute constructor for Shared memory blocks, as we must pass
* local references from entry-point function scope into the class

View File

@@ -67,8 +67,6 @@ const char *MTLLogParser::parse_line(const char *source_combined,
log_item.cursor.row++;
}
}
/* Count the needle end of line too. */
log_item.cursor.row++;
parsed_error_ = true;
}
}

View File

@@ -12,7 +12,7 @@
namespace blender::gpu {
const char *GLLogParser::parse_line(const char * /*source_combined*/,
const char *GLLogParser::parse_line(const char *source_combined,
const char *log_line,
GPULogItem &log_item)
{
@@ -54,13 +54,33 @@ const char *GLLogParser::parse_line(const char * /*source_combined*/,
log_item.cursor.source = log_item.cursor.row;
log_item.cursor.row = log_item.cursor.column;
log_item.cursor.column = -1;
log_item.source_base_row = true;
}
else {
/* line:char */
}
}
/* TODO: Temporary fix for new line directive. Eventually this whole parsing should be done in
* C++ with regex for simplicity. */
if (log_item.cursor.source != -1) {
StringRefNull src(source_combined);
std::string needle = std::string("#line 1 ") + std::to_string(log_item.cursor.source);
int64_t file_start = src.find(needle);
if (file_start == -1) {
/* Can be generated code or wrapper code outside of the main sources. */
log_item.cursor.row = -1;
}
else {
StringRef previous_sources(source_combined, file_start);
for (const char c : previous_sources) {
if (c == '\n') {
log_item.cursor.row++;
}
}
}
}
log_line = skip_separators(log_line, ":) ");
/* Skip to message. Avoid redundant info. */

View File

@@ -22,7 +22,7 @@ uint print_data(uint offset, float data)
return print_data(offset, floatBitsToUint(data));
}
uint print_header(uint format_hash, const uint data_len)
uint print_header(const uint data_len, uint format_hash)
{
uint offset = atomicAdd(gpu_print_buf[0], 1u + data_len) + 1u;
return print_data(offset, format_hash);

View File

@@ -1170,3 +1170,9 @@ RESHAPE(float3x3, float3x4, m[0].xyz, m[1].xyz, m[2].xyz)
#define VERTEX_SHADER_CREATE_INFO(a)
#define FRAGMENT_SHADER_CREATE_INFO(a)
#define COMPUTE_SHADER_CREATE_INFO(a)
#define _enum_type(name) name
#define _enum_decl(name) enum name {
#define _enum_end \
} \
;

View File

@@ -157,3 +157,8 @@ bool is_zero(vec4 A)
#define _inout_end
#define _shared_sta
#define _shared_end
#define _enum_dummy /* Needed to please glslang. */
#define _enum_type(name) uint
#define _enum_decl(name) const uint
#define _enum_end _enum_dummy;