Text Editor: add support for GLSL syntax highlighting
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 <foucault.clem@gmail.com>
This commit is contained in:
committed by
Campbell Barton
parent
a8cc6bb75b
commit
462c144f41
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
593
source/blender/editors/space_text/text_format_glsl.cc
Normal file
593
source/blender/editors/space_text/text_format_glsl.cc
Normal file
@@ -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 <cstring>
|
||||
|
||||
#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<const char *> 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<const char *> 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<const char *> 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));
|
||||
}
|
||||
|
||||
/** \} */
|
||||
@@ -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 <cstring>
|
||||
|
||||
Reference in New Issue
Block a user