From 462c144f414343ffbbac3546f1fae2bbf0bd52db Mon Sep 17 00:00:00 2001 From: Gangneron Date: Mon, 26 Feb 2024 11:40:24 +1100 Subject: [PATCH] Text Editor: add support for GLSL syntax highlighting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The objective is to be able to create your own GLSL shaders in Blender. This improves the workflow since all shader programming can be done directly in Blender. In addition, the GLSL language is a very popular language in the video games industry and even in general. Ref !116793 Co-authored-by: Clément Foucault --- .../blender/editors/space_text/CMakeLists.txt | 1 + .../blender/editors/space_text/space_text.cc | 1 + .../blender/editors/space_text/text_format.hh | 1 + .../editors/space_text/text_format_glsl.cc | 593 ++++++++++++++++++ .../editors/space_text/text_format_osl.cc | 3 + 5 files changed, 599 insertions(+) create mode 100644 source/blender/editors/space_text/text_format_glsl.cc diff --git a/source/blender/editors/space_text/CMakeLists.txt b/source/blender/editors/space_text/CMakeLists.txt index a9a274e3a00..9d7235a0965 100644 --- a/source/blender/editors/space_text/CMakeLists.txt +++ b/source/blender/editors/space_text/CMakeLists.txt @@ -21,6 +21,7 @@ set(SRC text_autocomplete.cc text_draw.cc text_format.cc + text_format_glsl.cc text_format_osl.cc text_format_pov.cc text_format_pov_ini.cc diff --git a/source/blender/editors/space_text/space_text.cc b/source/blender/editors/space_text/space_text.cc index d0241fe6939..06cb227514f 100644 --- a/source/blender/editors/space_text/space_text.cc +++ b/source/blender/editors/space_text/space_text.cc @@ -481,6 +481,7 @@ void ED_spacetype_text() BKE_spacetype_register(std::move(st)); /* register formatters */ + ED_text_format_register_glsl(); ED_text_format_register_py(); ED_text_format_register_osl(); ED_text_format_register_pov(); diff --git a/source/blender/editors/space_text/text_format.hh b/source/blender/editors/space_text/text_format.hh index 1294f32c822..a7c7edeb4a2 100644 --- a/source/blender/editors/space_text/text_format.hh +++ b/source/blender/editors/space_text/text_format.hh @@ -114,6 +114,7 @@ TextFormatType *ED_text_format_get(Text *text); void ED_text_format_register(TextFormatType *tft); /* formatters */ +void ED_text_format_register_glsl(); void ED_text_format_register_py(); void ED_text_format_register_osl(); void ED_text_format_register_pov(); diff --git a/source/blender/editors/space_text/text_format_glsl.cc b/source/blender/editors/space_text/text_format_glsl.cc new file mode 100644 index 00000000000..64b2eced625 --- /dev/null +++ b/source/blender/editors/space_text/text_format_glsl.cc @@ -0,0 +1,593 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup sptext + * + * Note that this formatter shares core logic with `text_format_osl.cc`, + * improvements here may apply there too. + */ + +#include + +#include "BLI_blenlib.h" + +#include "DNA_space_types.h" +#include "DNA_text_types.h" + +#include "BKE_text.h" + +#include "text_format.hh" + +/* -------------------------------------------------------------------- */ +/** \name Local Literal Definitions + * \{ */ + +/** + * GLSL builtin functions. + * https://registry.khronos.org/OpenGL/specs/gl/GLSLangSpec.4.60.pdf + */ +static const char *text_format_glsl_literals_builtinfunc_data[] = { + /* Force single column, sorted list. */ + /* clang-format off */ + "EmitStreamVertex", + "EmitVertex", + "EndPrimitive", + "EndStreamPrimitive", + "abs", + "acos", + "acosh", + "all", + "any", + "asin", + "asinh", + "atan", + "atanh", + "atomicAdd", + "atomicAnd", + "atomicCompSwap", + "atomicCounter", + "atomicCounterDecrement", + "atomicCounterIncrement", + "atomicExchange", + "atomicMax", + "atomicMin", + "atomicOr", + "atomicXor", + "barrier", + "bitCount", + "bitfieldExtract", + "bitfieldInsert", + "bitfieldReverse", + "bool", + "break", + "bvec2", + "bvec3", + "bvec4", + "case", + "ceil", + "clamp", + "continue", + "cos", + "cosh", + "cross", + "dFdx", + "dFdxCoarse", + "dFdxFine", + "dFdy", + "dFdyCoarse", + "dFdyFine", + "degrees", + "determinant", + "discard", + "distance", + "dmat2", + "dmat2x2", + "dmat2x3", + "dmat2x4", + "dmat3", + "dmat3x2", + "dmat3x3", + "dmat3x4", + "dmat4", + "dmat4x2", + "dmat4x3", + "dmat4x4", + "do", + "dot", + "double", + "else", + "equal", + "exp", + "exp2", + "faceforward", + "findLSB", + "findMSB", + "float", + "floatBitsToInt", + "floatBitsToUint", + "floor", + "fma", + "for", + "fract", + "frexp", + "fwidth", + "greaterThan", + "greaterThanEqual", + "groupMemoryBarrier", + "if", + "imageAtomicAdd", + "imageAtomicAnd", + "imageAtomicCompSwap", + "imageAtomicExchange", + "imageAtomicMax", + "imageAtomicMin", + "imageAtomicOr", + "imageAtomicXor", + "imageLoad", + "imageSamples", + "imageSize", + "imageStore", + "int", + "intBitsToFloat", + "interpolateAtCentriod", + "interpolateAtOffset", + "interpolateAtSample", + "inverse", + "inversesqrt", + "isinf", + "isnan", + "ivec2", + "ivec3", + "ivec4", + "ldexp", + "length", + "lessThan", + "lessThanEqual", + "log", + "log2", + "mat2", + "mat2x2", + "mat2x3", + "mat2x4", + "mat3", + "mat3x2", + "mat3x3", + "mat3x4", + "mat4", + "mat4x2", + "mat4x3", + "mat4x4", + "matrixCompMult", + "max", + "memoryBarrier", + "memoryBarrierAtomicCounter", + "memoryBarrierBuffer", + "memoryBarrierImage", + "memoryBarrierShared", + "min", + "mix", + "mod", + "modf", + "noise", + "normalize", + "not", + "notEqual", + "outerProduct", + "packDouble2x32", + "packHalf2x16", + "packUnorm", + "pow", + "radians", + "reflect", + "refract", + "return", + "round", + "roundEven", + "sampler1D", + "sampler1DArray", + "sampler1DArrayShadow", + "sampler1DShadow", + "sampler2D", + "sampler2DArray", + "sampler2DArrayShadow", + "sampler2DMS", + "sampler2DMSArray", + "sampler2DRect", + "sampler2DShadow", + "sampler3D", + "samplerBuffer", + "samplerCube", + "samplerCubeArray", + "samplerCubeArrayShadow", + "samplerCubeShadow", + "sign", + "sin", + "sinh", + "smoothstep", + "sqrt", + "step", + "struct", + "switch", + "tan", + "tanh", + "texelFetch", + "texelFetchOffset", + "texture", + "textureGather", + "textureGatherOffset", + "textureGatherOffsets", + "textureGrad", + "textureGradOffset", + "textureLod", + "textureLodOffset", + "textureOffset", + "textureProj", + "textureProjGrad", + "textureProjGradOffset", + "textureProjLod", + "textureProjLodOffset", + "textureProjOffset", + "textureQueryLevels", + "textureQueryLod", + "textureSamples", + "textureSize", + "transpose", + "trunc", + "uaddCarry", + "uint", + "uintBitsToFloat", + "umulExtended", + "unpackDouble2x32", + "unpackHalf2x16", + "unpackUnorm2x16", + "unpackUnorm4x8", + "usubBorrow", + "uvec2", + "uvec3", + "uvec4", + "vec2", + "vec3", + "vec4", + "void", + "while", + /* clang-format on */ +}; +static const Span text_format_glsl_literals_builtinfunc( + text_format_glsl_literals_builtinfunc_data, + ARRAY_SIZE(text_format_glsl_literals_builtinfunc_data)); + +/** + * GLSL reserved keywords. + * https://registry.khronos.org/OpenGL/specs/gl/GLSLangSpec.4.60.pdf + */ +static const char *text_format_glsl_literals_reserved_data[] = { + /* Force single column, sorted list. */ + /* clang-format off */ + "buffer", + "coherent", + "default", + "false", + "flat", + "in", + "inout", + "layout", + "out", + "readonly", + "restrict", + "sampler", + "smooth", + "true", + "uniform", + "varying", + "volatile", + "writeonly", + /* clang-format on */ +}; +static const Span text_format_glsl_literals_reserved( + text_format_glsl_literals_reserved_data, ARRAY_SIZE(text_format_glsl_literals_reserved_data)); + +/** + * GLSL special variables. + * https://registry.khronos.org/OpenGL/specs/gl/GLSLangSpec.4.60.pdf + */ +static const char *text_format_glsl_literals_specialvar_data[] = { + /* Force single column , sorted list */ + /* clang-format off */ + "gl_ClipDistance", + "gl_FragCoord", + "gl_FragDepth", + "gl_FrontFacing", + "gl_GlobalInvocationID", + "gl_InstanceID", + "gl_InvocationID", + "gl_Layer", + "gl_LocalInvocationID", + "gl_LocalInvocationIndex", + "gl_NumSamples", + "gl_NumWorkGroups", + "gl_PatchVerticesIn", + "gl_PointCoord", + "gl_PointSize", + "gl_Position", + "gl_PrimitiveID", + "gl_PrimitiveIDIn", + "gl_SampleID", + "gl_SampleMask", + "gl_SampleMaskIn", + "gl_SamplePosition", + "gl_TessCoord", + "gl_TessLevelInner", + "gl_TessLevelOuter", + "gl_VertexID", + "gl_ViewportIndex", + "gl_WorkGroupID", + "gl_WorkGroupSize", + /* clang-format on */ +}; +static const Span text_format_glsl_literals_specialvar( + text_format_glsl_literals_specialvar_data, + ARRAY_SIZE(text_format_glsl_literals_specialvar_data)); + +/** \} */ + +/*---------------------------------------------------------------------*/ +/* name local functions + */ + +static int txtfmt_glsl_find_builtinfunc(const char *string) +{ + const int i = text_format_string_literal_find(text_format_glsl_literals_builtinfunc, string); + + if (i == 0 || text_check_identifier(string[i])) { + return -1; + } + return i; +} + +static int txtfmt_glsl_find_reserved(const char *string) +{ + const int i = text_format_string_literal_find(text_format_glsl_literals_reserved, string); + + if (i == 0 || text_check_identifier(string[i])) { + return -1; + } + return i; +} +static int txtfmt_glsl_find_specialvar(const char *string) +{ + const int i = text_format_string_literal_find(text_format_glsl_literals_specialvar, string); + + if (i == 0 || text_check_identifier(string[i])) { + return -1; + } + return i; +} +static int txtfmt_glsl_find_preprocessor(const char *string) +{ + if (string[0] == '#') { + int i = 1; + /* White-space is ok '# foo'. */ + while (text_check_whitespace(string[i])) { + i++; + } + while (text_check_identifier(string[i])) { + i++; + } + return i; + } + return -1; +} +static char txtfmt_glsl_format_identifier(const char *str) +{ + char fmt; + + /* clang-format off */ + + if (txtfmt_glsl_find_specialvar(str) != -1) {fmt = FMT_TYPE_SPECIAL; + } else if (txtfmt_glsl_find_builtinfunc(str) != -1) {fmt = FMT_TYPE_KEYWORD; + } else if (txtfmt_glsl_find_reserved(str) != -1) {fmt = FMT_TYPE_RESERVED; + } else if (txtfmt_glsl_find_preprocessor(str) != -1) {fmt = FMT_TYPE_DIRECTIVE; + } else {fmt = FMT_TYPE_DEFAULT; + } + + /* clang-format on */ + + return fmt; +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Format Line Implementation (#TextFormatType::format_line) + * \{ */ + +static void txtfmt_glsl_format_line(SpaceText *st, TextLine *line, const bool do_next) +{ + FlattenString fs; + const char *str; + char *fmt; + char cont_orig, cont, find, prev = ' '; + int len, i; + + /* Get continuation from previous line */ + if (line->prev && line->prev->format != nullptr) { + fmt = line->prev->format; + cont = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */ + BLI_assert((FMT_CONT_ALL & cont) == cont); + } + else { + cont = FMT_CONT_NOP; + } + + /* Get original continuation from this line */ + if (line->format != nullptr) { + fmt = line->format; + cont_orig = fmt[strlen(fmt) + 1]; /* Just after the null-terminator */ + BLI_assert((FMT_CONT_ALL & cont_orig) == cont_orig); + } + else { + cont_orig = 0xFF; + } + + len = flatten_string(st, &fs, line->line); + str = fs.buf; + if (!text_check_format_len(line, len)) { + flatten_string_free(&fs); + return; + } + fmt = line->format; + + while (*str) { + /* Handle escape sequences by skipping both \ and next char */ + if (*str == '\\') { + *fmt = prev; + fmt++; + str++; + if (*str == '\0') { + break; + } + *fmt = prev; + fmt++; + str += BLI_str_utf8_size_safe(str); + continue; + } + /* Handle continuations */ + if (cont) { + /* C-Style comments */ + if (cont & FMT_CONT_COMMENT_C) { + if (*str == '*' && *(str + 1) == '/') { + *fmt = FMT_TYPE_COMMENT; + fmt++; + str++; + *fmt = FMT_TYPE_COMMENT; + cont = FMT_CONT_NOP; + } + else { + *fmt = FMT_TYPE_COMMENT; + } + /* Handle other comments */ + } + else { + find = (cont & FMT_CONT_QUOTEDOUBLE) ? '"' : '\''; + if (*str == find) { + cont = 0; + } + *fmt = FMT_TYPE_STRING; + } + + str += BLI_str_utf8_size_safe(str) - 1; + } + /* Not in a string... */ + else { + /* Deal with comments first */ + if (*str == '/' && *(str + 1) == '/') { + /* fill the remaining line */ + text_format_fill(&str, &fmt, FMT_TYPE_COMMENT, len - int(fmt - line->format)); + } + /* C-Style (multi-line) comments */ + else if (*str == '/' && *(str + 1) == '*') { + cont = FMT_CONT_COMMENT_C; + *fmt = FMT_TYPE_COMMENT; + fmt++; + str++; + *fmt = FMT_TYPE_COMMENT; + } + else if (ELEM(*str, '"', '\'')) { + /* Strings */ + find = *str; + cont = (*str == '"') ? FMT_CONT_QUOTEDOUBLE : FMT_CONT_QUOTESINGLE; + *fmt = FMT_TYPE_STRING; + } + /* White-space (all white-space has been converted to spaces). */ + else if (*str == ' ') { + *fmt = FMT_TYPE_WHITESPACE; + } + /* Numbers (digits not part of an identifier and periods followed by digits) */ + else if ((prev != FMT_TYPE_DEFAULT && text_check_digit(*str)) || + (*str == '.' && text_check_digit(*(str + 1)))) + { + *fmt = FMT_TYPE_NUMERAL; + } + /* Punctuation */ + else if ((*str != '#') && text_check_delim(*str)) { + *fmt = FMT_TYPE_SYMBOL; + } + /* Identifiers and other text (no previous white-space or delimiters. so text continues). */ + else if (prev == FMT_TYPE_DEFAULT) { + str += BLI_str_utf8_size_safe(str) - 1; + *fmt = FMT_TYPE_DEFAULT; + } + /* Not white-space, a digit, punctuation, or continuing text. + * Must be new, check for special words. */ + else { + /* Keep aligned arguments for readability. */ + /* clang-format off */ + + /* Special vars(v) or built-in keywords(b) */ + /* keep in sync with `txtfmt_glsl_format_identifier()`. */ + if ((i = txtfmt_glsl_find_specialvar(str)) != -1) { prev = FMT_TYPE_SPECIAL; + } else if ((i = txtfmt_glsl_find_builtinfunc(str)) != -1) { prev = FMT_TYPE_KEYWORD; + } else if ((i = txtfmt_glsl_find_reserved(str)) != -1) { prev = FMT_TYPE_RESERVED; + } else if ((i = txtfmt_glsl_find_preprocessor(str)) != -1) { prev = FMT_TYPE_DIRECTIVE; + } + /* clang-format on */ + + if (i > 0) { + if (prev == FMT_TYPE_DIRECTIVE) { /* can contain utf8 */ + text_format_fill(&str, &fmt, prev, i); + } + else { + text_format_fill_ascii(&str, &fmt, prev, i); + } + } + else { + str += BLI_str_utf8_size_safe(str) - 1; + *fmt = FMT_TYPE_DEFAULT; + } + } + } + prev = *fmt; + fmt++; + str++; + } + + /* Terminate and add continuation char. */ + *fmt = '\0'; + fmt++; + *fmt = cont; + + /* If continuation has changed and we're allowed, process the next line */ + if (cont != cont_orig && do_next && line->next) { + txtfmt_glsl_format_line(st, line->next, do_next); + } + + flatten_string_free(&fs); +} + +/** \} */ + +/* -------------------------------------------------------------------- */ +/** \name Registration + * \{ */ + +void ED_text_format_register_glsl() +{ + static TextFormatType tft = {nullptr}; + static const char *ext[] = {"glsl", nullptr}; + + tft.format_identifier = txtfmt_glsl_format_identifier; + tft.format_line = txtfmt_glsl_format_line; + tft.ext = ext; + tft.comment_line = "//"; + + ED_text_format_register(&tft); + + BLI_assert( + text_format_string_literals_check_sorted_array(text_format_glsl_literals_builtinfunc)); + BLI_assert(text_format_string_literals_check_sorted_array(text_format_glsl_literals_reserved)); + BLI_assert(text_format_string_literals_check_sorted_array(text_format_glsl_literals_specialvar)); +} + +/** \} */ diff --git a/source/blender/editors/space_text/text_format_osl.cc b/source/blender/editors/space_text/text_format_osl.cc index 18e7777bef7..3cc2647e5bc 100644 --- a/source/blender/editors/space_text/text_format_osl.cc +++ b/source/blender/editors/space_text/text_format_osl.cc @@ -4,6 +4,9 @@ /** \file * \ingroup sptext + * + * Note that this formatter shares core logic with `text_format_glsl.cc`, + * improvements here may apply there too. */ #include