//
// This unit is part of the GLScene Engine, http://glscene.org
//

unit FShaderMemo;

(*
  Shader code editor.
// TODO: need to decide how to load templates from external file
//       and update it without package recompilation

*)

interface

{$I GLScene.inc}

uses
  Winapi.Windows,
  Winapi.Messages,
  System.SysUtils,
  System.Variants,
  System.Classes,
  System.Win.Registry,
  VCL.Controls,
  VCL.Forms,
  VCL.ComCtrls,
  VCL.ImgList,
  VCL.Dialogs,
  VCL.Menus,
  VCL.ActnList,
  VCL.ToolWin,
  VCL.ExtCtrls,
  VCL.StdCtrls,
  VCL.Graphics,
   
  GLS.Memo;

type

  TShaderMemoForm = class(TForm)
    ImageList: TImageList;
    ToolBar: TToolBar;
    TBOpen: TToolButton;
    TBSave: TToolButton;
    TBStayOnTop: TToolButton;
    TBHelp: TToolButton;
    ToolButton2: TToolButton;
    TBCopy: TToolButton;
    TBPaste: TToolButton;
    TBCut: TToolButton;
    ToolButton10: TToolButton;
    TBTemplate: TToolButton;
    TBUndo: TToolButton;
    TBRedo: TToolButton;
    ToolButton4: TToolButton;
    GLSLMemo: TGLSSynHiMemo;
    OpenDialog: TOpenDialog;
    SaveDialog: TSaveDialog;
    TemplateMenu: TPopupMenu;
    GLSL120: TMenuItem;
    GLSL330: TMenuItem;
    GLSL400: TMenuItem;
    N1: TMenuItem;
    N2: TMenuItem;
    CompilatorLog: TMemo;
    TBIncIndent: TToolButton;
    TBDecIndent: TToolButton;
    TBComment: TToolButton;
    TBUncoment: TToolButton;
    ToolButton1: TToolButton;
    Panel1: TPanel;
    CancelButton: TButton;
    OKButton: TButton;
    CheckButton: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure GLSLMemoGutterClick(Sender: TObject; LineNo: Integer);
    procedure GLSLMemoGutterDraw(Sender: TObject; ACanvas: TCanvas; LineNo: Integer; rct: TRect);
    procedure TBOpenClick(Sender: TObject);
    procedure TBSaveClick(Sender: TObject);
    procedure TBStayOnTopClick(Sender: TObject);
    procedure TBUndoClick(Sender: TObject);
    procedure GLSLMemoUndoChange(Sender: TObject; CanUndo, CanRedo: Boolean);
    procedure TBRedoClick(Sender: TObject);
    procedure TBCopyClick(Sender: TObject);
    procedure TBPasteClick(Sender: TObject);
    procedure TBCutClick(Sender: TObject);
    procedure CheckButtonClick(Sender: TObject);
    procedure TBIncIndentClick(Sender: TObject);
    procedure TBDecIndentClick(Sender: TObject);
    procedure TBCommentClick(Sender: TObject);
    procedure TBUncomentClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
  private
    FLightLineStyle: Integer;
    FOnCheck: TNotifyEvent;
    procedure OnTemplateClick(Sender: TObject);
  public
    property OnCheck: TNotifyEvent read FOnCheck write FOnCheck;
  end;

function GLShaderEditorForm: TShaderMemoForm;
procedure ReleaseGLShaderEditor;

//------------------------------------------------------------------
implementation
//------------------------------------------------------------------

{$R *.dfm}

const
  cRegistryKey = 'Software\GLScene\GLSceneShaderEdit';

// ---------------------Syntax keywords 

const

  GLSLDirectives: array[0..12] of string =
    (
    '#define',
    '#undef',
    '#if',
    '#ifdef',
    '#ifndef',
    '#else',
    '#elif',
    '#endif',
    '#error',
    '#pragma',
    '#extension',
    '#version',
    '#line'
    );

  GLSLQualifiers: array[0..33] of string =
    (
    'attribute', 'const', 'uniform', 'varying',
    'layout',
    'centroid', 'flat', 'smooth', 'noperspective',
    'patch', 'sample',
    'break', 'continue', 'do', 'for', 'while', 'switch', 'case', 'default',
    'if', 'else',
    'subroutine',
    'in', 'out', 'inout',
    'true', 'false',
    'invariant',
    'discard', 'return',
    'lowp', 'mediump', 'highp', 'precision'
    );

  GLSLTypes: array[0..85] of string =
    (
    'float', 'double', 'int', 'void', 'bool',
    'mat2', 'mat3', 'mat4', 'dmat2', 'dmat3', 'dmat4',
    'mat2x2', 'mat2x3', 'mat2x4', 'dmat2x2', 'dmat2x3', 'dmat2x4',
    'mat3x2', 'mat3x3', 'mat3x4', 'dmat3x2', 'dmat3x3', 'dmat3x4',
    'mat4x2', 'mat4x3', 'mat4x4', 'dmat4x2', 'dmat4x3', 'dmat4x4',
    'vec2', 'vec3', 'vec4', 'ivec2', 'ivec3', 'ivec4', 'bvec2', 'bvec3', 'bvec4', 'dvec2', 'dvec3', 'dvec4',
    'uint', 'uvec2', 'uvec3', 'uvec4',
    'sampler1D', 'sampler2D', 'sampler3D', 'samplerCube',
    'sampler1DShadow', 'sampler2DShadow', 'samplerCubeShadow',
    'sampler1DArray', 'sampler2DArray',
    'sampler1DArrayShadow', 'sampler2DArrayShadow',
    'isampler1D', 'isampler2D', 'isampler3D', 'isamplerCube',
    'isampler1DArray', 'isampler2DArray',
    'usampler1D', 'usampler2D', 'usampler3D', 'usamplerCube',
    'usampler1DArray', 'usampler2DArray',
    'sampler2DRect', 'sampler2DRectShadow', 'isampler2DRect', 'usampler2DRect',
    'samplerBuffer', 'isamplerBuffer', 'usamplerBuffer',
    'sampler2DMS', 'isampler2DMS', 'usampler2DMS',
    'sampler2DMSArray', 'isampler2DMSArray', 'usampler2DMSArray',
    'samplerCubeArray', 'samplerCubeArrayShadow', 'isamplerCubeArray', 'usamplerCubeArray',
    'struct'
    );

  GLSLBuildIn: array[0..117] of string =
    (
    'gl_Color',
    'gl_SecondaryColor',
    'gl_Normal',
    'gl_Vertex',
    'gl_MultiTexCoord0',
    'gl_MultiTexCoord1',
    'gl_MultiTexCoord2',
    'gl_MultiTexCoord3',
    'gl_MultiTexCoord4',
    'gl_MultiTexCoord5',
    'gl_MultiTexCoord6',
    'gl_MultiTexCoord7',
    'gl_FogCoord',
    'gl_FrontColor',
    'gl_BackColor',
    'gl_FrontSecondaryColor',
    'gl_BackSecondaryColor',
    'gl_TexCoord',
    'gl_FogFragCoord',
    'gl_PointCoord',
    'gl_Position',
    'gl_PointSize',
    'gl_ClipVertex',
    'gl_FragCoord',
    'gl_FrontFacing',
    'gl_FragColor',
    'gl_FragData',
    'gl_FragDepth',
    'gl_VertexID',
    'gl_InstanceID',
    'gl_in',
    'gl_out',
    'gl_PrimitiveIDIn',
    'gl_InvocationID',
    'gl_PrimitiveID',
    'gl_Layer',
    'gl_PatchVerticesIn',
    'gl_TessLevelOuter',
    'gl_TessLevelInner',
    'gl_TessCoord',
    'gl_SampleID',
    'gl_SamplePosition',
    'gl_SampleMask',
    'gl_FrontFacing',
    'gl_ClipDistance',
    'gl_MaxVertexAttribs',
    'gl_MaxVertexUniformComponents',
    'gl_MaxVaryingFloats',
    'gl_MaxVaryingComponents',
    'gl_MaxVertexOutputComponents',
    'gl_MaxGeometryInputComponents',
    'gl_MaxGeometryOutputComponents',
    'gl_MaxFragmentInputComponents',
    'gl_MaxVertexTextureImageUnits',
    'gl_MaxCombinedTextureImageUnits',
    'gl_MaxTextureImageUnits',
    'gl_MaxFragmentUniformComponents',
    'gl_MaxDrawBuffers',
    'gl_MaxClipDistances',
    'gl_MaxGeometryTextureImageUnits',
    'gl_MaxGeometryOutputVertices',
    'gl_MaxGeometryTotalOutputComponents',
    'gl_MaxGeometryUniformComponents',
    'gl_MaxGeometryVaryingComponents',
    'gl_MaxTessControlInputComponents',
    'gl_MaxTessControlOutputComponents',
    'gl_MaxTessControlTextureImageUnits',
    'gl_MaxTessControlUniformComponents',
    'gl_MaxTessControlTotalOutputComponents',
    'gl_MaxTessEvaluationInputComponents',
    'gl_MaxTessEvaluationOutputComponents',
    'gl_MaxTessEvaluationTextureImageUnits',
    'gl_MaxTessEvaluationUniformComponents',
    'gl_MaxTessPatchComponents',
    'gl_MaxPatchVertices',
    'gl_MaxTessGenLevel',
    'gl_MaxTextureUnits',
    'gl_MaxTextureCoords',
    'gl_MaxClipPlanes',
    'gl_DepthRange',
    'gl_ModelViewMatrix',
    'gl_ProjectionMatrix',
    'gl_ModelViewProjectionMatrix',
    'gl_TextureMatrix',
    'gl_NormalMatrix',
    'gl_ModelViewMatrixInverse',
    'gl_ProjectionMatrixInverse',
    'gl_ModelViewProjectionMatrixInverse',
    'gl_TextureMatrixInverse',
    'gl_ModelViewMatrixTranspose',
    'gl_ProjectionMatrixTranspose',
    'gl_ModelViewProjectionMatrixTranspose',
    'gl_TextureMatrixTranspose',
    'gl_ModelViewMatrixInverseTranspose',
    'gl_ProjectionMatrixInverseTranspose',
    'gl_ModelViewProjectionMatrixInverseTranspose',
    'gl_TextureMatrixInverseTranspose',
    'gl_NormalScale',
    'gl_ClipPlane',
    'gl_Point',
    'gl_FrontMaterial',
    'gl_BackMaterial',
    'gl_LightSource',
    'gl_LightModel',
    'gl_FrontLightModelProduct',
    'gl_BackLightModelProduct',
    'gl_FrontLightProduct',
    'gl_BackLightProduct',
    'gl_TextureEnvColor',
    'gl_EyePlaneS',
    'gl_EyePlaneT',
    'gl_EyePlaneR',
    'gl_EyePlaneQ',
    'gl_ObjectPlaneS',
    'gl_ObjectPlaneT',
    'gl_ObjectPlaneR',
    'gl_ObjectPlaneQ',
    'gl_Fog'
    );

  GLSLFunctions: array[0..139] of string =
    (

    // Angle and Trigonometry Functions
    'radians',
    'degrees',
    'sin',
    'cos',
    'tan',
    'asin',
    'acos',
    'atan',
    'sinh',
    'cosh',
    'tanh',
    'asinh',
    'acosh',
    'atanh',

    // Exponetial
    'pow',
    'exp',
    'log',
    'exp2',
    'log2',
    'sqrt',
    'inversesqrt',

    // Common
    'abs',
    'sign',
    'floor',
    'trunc',
    'round',
    'roundEven',
    'ceil',
    'fract',
    'mod',
    'min',
    'max',
    'clamp',
    'mix',
    'step',
    'smoothstep',
    'isnan',
    'isinf',
    'floatBitsToInt',
    'floatBitsToUint',
    'intBitsToFloat',
    'uintBitsToFloat',
    'fma',
    'frexp',
    'ldexp',

    // Floating-Point Pack and Unpack Functions
    'packUnorm2x16',
    'packUnorm4x8',
    'packSnorm4x8',
    'unpackUnorm2x16',
    'unpackUnorm4x8',
    'unpackSnorm4x8',
    'packDouble2x32',
    'unpackDouble2x32',

    // Geometric
    'length',
    'distance',
    'dot',
    'cross',
    'normalize',
    'ftransform',
    'faceforward',
    'reflect',
    'maxtrixCompMult',
    'outerProduct',
    'transpose',
    'determinant',
    'inverse',

    // Vector
    'lessThan',
    'lessThanEqual',
    'greaterThan',
    'greaterThanEqual',
    'equal',
    'notEqual',
    'any',
    'all',
    'not',

    // Integer Functions
    'uaddCarry',
    'usubBorrow',
    'umulExtended',
    'bitfieldExtract',
    'bitfieldInsert',
    'bitfieldReverse',
    'bitCount',
    'findLSB',
    'findMSB',

    // Texture
    'texture1D',
    'texture1DProj',
    'texture1DLod',
    'texture1DProjLod',
    'texture2D',
    'texture2DProj',
    'texture2DLod',
    'texture2DProjLod',
    'texture3D',
    'texture3DProj',
    'texture3DLod',
    'texture3DProjLod',
    'textureCube',
    'textureCubeLod',
    'shadow1D',
    'shadow1DProj',
    'shadow1DLod',
    'shadow1DProjLod',
    'shadow2D',
    'shadow2DProj',
    'shadow2DLod',
    'shadow2DProjLod',
    'textureSize',
    'textureQueryLod',
    'texture',
    'textureProj',
    'textureLod',
    'textureOffset',
    'texelFetch',
    'texelFetchOffset',
    'textureProjOffset',
    'textureLodOffset',
    'textureProjLod',
    'textureProjLodOffset',
    'textureGrad',
    'textureGradOffset',
    'textureProjGrad',
    'textureProjGradOffset',
    'textureGather',
    'textureGatherOffset',
    'textureGatherOffsets',

    // Fragment
    'dFdx',
    'dFdy',
    'fwidth',

    // Interpolation Functions
    'interpolateAtCentroid',
    'interpolateAtSample',
    'interpolateAtOffset',
    // Noise
    'noise1',
    'noise2',
    'noise3',
    'noise4',

    // Geometry Shader Functions
    'EmitStreamVertex',
    'EndStreamPrimitive',
    'EmitVertex',
    'EndPrimitive',

    // Shader Invocation Control Functions
    'barrier'
    );
 

// ---------------------Shader template 
const
  cTemplates: array[0..6] of string =
  (
    '#version 120'#10#13+
    #10#13+
    'attribute vec3 Position;'#10#13+
    'attribute vec3 Normal;'#10#13+
    'varying vec3 v2f_Normal;'#10#13+
    'varying vec3 v2f_LightDir;'#10#13+
    'varying vec3 v2f_ViewDir;'#10#13+
    #10#13+
    'uniform mat4 ModelMatrix;'#10#13+
    'uniform mat4 ViewProjectionMatrix;'#10#13+
    'uniform mat3 NormalMatrix;'#10#13+
    'uniform vec4 LightPosition;'#10#13+
    'uniform vec4 CameraPosition;'#10#13+
    #10#13+
    'void main()'#10#13+
    '{'#10#13+
    '  vec4 WorldPos = ModelMatrix * vec4(Position,1.0);'#10#13+
    '  gl_Position = ViewProjectionMatrix * WorldPos;'#10#13+
    '  v2f_Normal = NormalMatrix * Normal;'#10#13+
    '  v2f_LightDir = LightPosition.xyz - WorldPos.xyz;'#10#13+
    '  v2f_ViewDir = CameraPosition.xyz - WorldPos.xyz;'#10#13+
    '}'#10#13,
//-----------------------------------------------------------------------
    '#version 120'#10#13+
    #10#13+
    'attribute vec3 Position;'#10#13+
    'attribute vec3 Normal;'#10#13+
    'attribute vec3 Tangent;'#10#13+
    'attribute vec3 Binormal;'#10#13+
    'attribute vec2 TexCoord0;'#10#13+
    #10#13+
    'varying vec2 v2f_TexCoord;'#10#13+
    'varying vec3 v2f_Normal;'#10#13+
    'varying vec3 v2f_Tangent;'#10#13+
    'varying vec3 v2f_Binormal;'#10#13+
    'varying vec3 v2f_LightDir;'#10#13+
    'varying vec3 v2f_ViewDir;'#10#13+
    #10#13+
    'uniform mat4 ModelMatrix;'#10#13+
    'uniform mat4 ViewProjectionMatrix;'#10#13+
    'uniform vec4 LightPosition;'#10#13+
    'uniform vec4 CameraPosition;'#10#13+
    'uniform vec2 BaseTextureRepeat;'#10#13+
    #10#13+
    'void main()'#10#13+
    '{'#10#13+
    '  vec3 WorldPos = (ModelMatrix * vec4(Position,1.0)).xyz;'#10#13+
    '  gl_Position = ViewProjectionMatrix * vec4(WorldPos, 1.0);'#10#13+
    '  v2f_TexCoord = TexCoord0.xy*BaseTextureRepeat;'#10#13+
    '  v2f_Normal = Normal;'#10#13+
    '  v2f_Tangent = Tangent;'#10#13+
    '  v2f_Binormal = Binormal;'#10#13+
    '  v2f_LightDir = LightPosition.xyz - WorldPos.xyz;'#10#13+
    '  v2f_ViewDir = CameraPosition.xyz - WorldPos.xyz;'#10#13+
    '}'#10#13,
//-----------------------------------------------------------------------
    '#version 120'#10#13+
    #10#13+
    'varying vec3 v2f_Normal;'#10#13+
    'varying vec3 v2f_LightDir;'#10#13+
    'varying vec3 v2f_ViewDir;'#10#13+
    #10#13+
    'uniform vec4 MaterialAmbientColor;'#10#13+
    'uniform vec4 MaterialDiffuseColor;'#10#13+
    'uniform vec4 MaterialSpecularColor;'#10#13+
    'uniform float MaterialShiness;'#10#13+
    #10#13+
    'void main()'#10#13+
    '{'#10#13+
    '  vec3 N = normalize(v2f_Normal);'#10#13+
    #10#13+
    '  vec3 L = normalize(v2f_LightDir);'#10#13+
    '  float DiffuseIntensity = max( dot( N, L ), 0.0);'#10#13+
    '  vec4  cDiffuse = DiffuseIntensity * MaterialDiffuseColor;'#10#13+
    #10#13+
    '  vec4 cSpecular = vec4(0.0);'#10#13+
    '  if (DiffuseIntensity > 0.0)'#10#13+
    '  {'#10#13+
    '    vec3 V = normalize(v2f_ViewDir);'#10#13+
    '    vec3 R = reflect(-V, N);'#10#13+
    '    float RdotL = max( dot( L, R ), 0.0);'#10#13+
    '    if (RdotL > 0.0)'#10#13+
    '      cSpecular = pow( RdotL, MaterialShiness) * MaterialSpecularColor;'#10#13+
    '  }'#10#13+
    #10#13+
    '  gl_FragColor = MaterialAmbientColor + cDiffuse  + cSpecular;'#10#13+
    '}'#10#13,
//-----------------------------------------------------------------------
    '#version 120'#10#13+
    #10#13+
    'varying vec3 v2f_Normal;'#10#13+
    'varying vec3 v2f_Tangent;'#10#13+
    'varying vec3 v2f_Binormal;'#10#13+
    'varying vec3 v2f_LightDir;'#10#13+
    'varying vec3 v2f_ViewDir;'#10#13+
    'varying vec2 v2f_TexCoord;'#10#13+
    #10#13+
    'uniform mat3 NormalMatrix;'#10#13+
    'uniform sampler2D DiffuseMap;'#10#13+
    'uniform sampler2D NormalMap;'#10#13+
    #10#13+
    'uniform vec4 MaterialAmbientColor;'#10#13+
    'uniform vec4 MaterialDiffuseColor;'#10#13+
    'uniform vec4 MaterialSpecularColor;'#10#13+
    'uniform float MaterialShiness;'#10#13+
    #10#13+
    'void main()'#10#13+
    '{'#10#13+
    '  vec3 normal = normalize(v2f_Normal);'#10#13+
    '  vec3 tangent = normalize(v2f_Tangent);'#10#13+
    '  vec3 binormal = normalize(v2f_Binormal);'#10#13+
    '  mat3 basis = mat3(tangent, binormal, normal);'#10#13+
    '  vec3 N = texture2D(NormalMap, v2f_TexCoord).xyz;'#10#13+
    '  N = N * vec3(2.0) - vec3(1.0);'#10#13+
    '  N = basis*N;'#10#13+
    '  N = NormalMatrix*N;'#10#13+
    '  N = normalize(N);'#10#13+
    #10#13+
    '  vec3 L = normalize(v2f_LightDir);'#10#13+
    '  float DiffuseIntensity = max( dot( N, L ), 0.0);'#10#13+
    '  vec4  cDiffuse = DiffuseIntensity * MaterialDiffuseColor;'#10#13+
    #10#13+
    '  vec4 cSpecular = vec4(0.0);'#10#13+
    '  if (DiffuseIntensity > 0.0)'#10#13+
    '  {'#10#13+
    '    vec3 V = normalize(v2f_ViewDir);'#10#13+
    '    vec3 R = reflect(-V, N);'#10#13+
    '    float RdotL = max( dot( L, R ), 0.0);'#10#13+
    '    if (RdotL > 0.0)'#10#13+
    '      cSpecular = pow( RdotL, MaterialShiness) * MaterialSpecularColor;'#10#13+
    '  }'#10#13+
    #10#13+
    '  vec4 cBaseColor = texture2D( DiffuseMap, v2f_TexCoord);'#10#13+
    '  gl_FragColor = (MaterialAmbientColor + cDiffuse ) * cBaseColor + cSpecular;'#10#13+
    '}'#10#13,
//-----------------------------------------------------------------------
    '#version 330'#10#13+
    'layout(triangles_adjacency) in;'#10#13+
    'layout(line_strip, max_vertices = 6) out;'#10#13+
    'in vec4 v2g_WorldPos[]; // Vertex position in view space'#10#13+
    'uniform vec4 CameraPosition;'#10#13+
    #10#13+
    '// calculating facing of a triangle relative to eye'#10#13+
    'float facing(vec4 v0, vec4 v1, vec4 v2, vec4 eye_pos)'#10#13+
    '{'#10#13+
    '    vec3 e0 = v1.xyz - v0.xyz;'#10#13+
    '    vec3 e1 = v2.xyz - v0.xyz;'#10#13+
    '    vec4 p;'#10#13+
    '    p.xyz = cross(e1, e0);'#10#13+
    '    p.w = -dot(v0.xyz, p.xyz);'#10#13+
    '    return -dot(p, eye_pos);'#10#13+
    '}'#10#13+
    #10#13+
    '// output lines on silhouette edges by comparing facing of adjacent triangles'#10#13+
    'void main()'#10#13+
    '{'#10#13+
    '    float f = facing(v2g_WorldPos[0], v2g_WorldPos[2], v2g_WorldPos[4], CameraPosition);'#10#13+
    '    // only look at front facing triangles'#10#13+
    '    if (f > 0.0)'#10#13+
    '    {'#10#13+
    '        float f;'#10#13+
    '        // test edge 0'#10#13+
    '        f = facing(v2g_WorldPos[0], v2g_WorldPos[1], v2g_WorldPos[2], CameraPosition);'#10#13+
    '        if (f <= 0)'#10#13+
    '        {'#10#13+
    '            gl_Position = gl_in[0].gl_Position;'#10#13+
    '            EmitVertex();'#10#13+
    '            gl_Position = gl_in[2].gl_Position;'#10#13+
    '            EmitVertex();'#10#13+
    '            EndPrimitive();'#10#13+
    '        }'#10#13+
    #10#13+
    '        // test edge 1'#10#13+
    '        f = facing(v2g_WorldPos[2], v2g_WorldPos[3], v2g_WorldPos[4], CameraPosition);'#10#13+
    '        if (f <= 0.0)'#10#13+
    '        {'#10#13+
    '            gl_Position = gl_in[2].gl_Position;'#10#13+
    '            EmitVertex();'#10#13+
    '            gl_Position = gl_in[4].gl_Position;'#10#13+
    '            EmitVertex();'#10#13+
    '            EndPrimitive();'#10#13+
    '        }'#10#13+
    #10#13+
    '        // test edge 2'#10#13+
    '        f = facing(v2g_WorldPos[4], v2g_WorldPos[5], v2g_WorldPos[0], CameraPosition);'#10#13+
    '        if (f <= 0.0)'#10#13+
    '        {'#10#13+
    '            gl_Position = gl_in[4].gl_Position;'#10#13+
    '            EmitVertex();'#10#13+
    '            gl_Position = gl_in[0].gl_Position;'#10#13+
    '            EmitVertex();'#10#13+
    '            EndPrimitive();'#10#13+
    '        }'#10#13+
    '    }'#10#13+
    '}'#10#13,
//-----------------------------------------------------------------------
    'attribute vec3 Position;'#10#13+
    'attribute vec4 Color;'#10#13+
    'attribute vec3 Normal;'#10#13+
    'attribute vec3 Tangent;'#10#13+
    'attribute vec3 Binormal;'#10#13+
    'attribute vec2 TexCoord0;'#10#13+
    'attribute vec2 TexCoord1;'#10#13+
    'attribute vec2 TexCoord2;'#10#13+
    'attribute vec2 TexCoord3;'#10#13+
    'attribute vec2 TexCoord4;'#10#13+
    'attribute vec2 TexCoord5;'#10#13+
    'attribute vec2 TexCoord6;'#10#13+
    'attribute vec2 TexCoord7;'#10#13+
    'attribute vec4 Custom0;'#10#13+
    'attribute vec2 Custom1;'#10#13+
    'attribute vec2 Custom2;'#10#13,
//-----------------------------------------------------------------------
    'in vec3 Position;'#10#13+
    'in vec4 Color;'#10#13+
    'in vec3 Normal;'#10#13+
    'in vec3 Tangent;'#10#13+
    'in vec3 Binormal;'#10#13+
    'in vec2 TexCoord0;'#10#13+
    'in vec2 TexCoord1;'#10#13+
    'in vec2 TexCoord2;'#10#13+
    'in vec2 TexCoord3;'#10#13+
    'in vec2 TexCoord4;'#10#13+
    'in vec2 TexCoord5;'#10#13+
    'in vec2 TexCoord6;'#10#13+
    'in vec2 TexCoord7;'#10#13+
    'in vec4 Custom0;'#10#13+
    'in vec2 Custom1;'#10#13+
    'in vec2 Custom2;'#10#13
  );

 

type
  TFriendlyMemo = class(TGLSSynHiMemo);

var
  vGLShaderEditor: TShaderMemoForm;

function GLShaderEditorForm: TShaderMemoForm;
begin
  if not Assigned(vGLShaderEditor) then
    vGLShaderEditor := TShaderMemoForm.Create(nil);
  Result := vGLShaderEditor;
end;

procedure ReleaseGLShaderEditor;
begin
  if Assigned(vGLShaderEditor) then
  begin
    vGLShaderEditor.Free;
    vGLShaderEditor := nil;
  end;
end;

function ReadRegistryInteger(reg: TRegistry; const name: string;
  defaultValue: Integer): Integer;
begin
  if reg.ValueExists(name) then
    Result := reg.ReadInteger(name)
  else
    Result := defaultValue;
end;

procedure TShaderMemoForm.FormCreate(Sender: TObject);
var
  reg: TRegistry;
  No: Integer;
  item: TMenuItem;
begin
  reg := TRegistry.Create;
  try
    if reg.OpenKey(cRegistryKey, True) then
    begin
      Left := ReadRegistryInteger(reg, 'Left', Left);
      Top := ReadRegistryInteger(reg, 'Top', Top);
      Width := ReadRegistryInteger(reg, 'Width', 500);
      Height := ReadRegistryInteger(reg, 'Height', 640);
    end;
  finally
    reg.Free;
  end;

  No := GLSLMemo.Styles.Add(clRed, clWhite, []);
  GLSLMemo.AddWord(No, GLSLDirectives);

  No := GLSLMemo.Styles.Add(clPurple, clWhite, [fsBold]);
  GLSLMemo.AddWord(No, GLSLQualifiers);

  No := GLSLMemo.Styles.Add(clBlue, clWhite, [fsBold]);
  GLSLMemo.AddWord(No, GLSLTypes);

  No := GLSLMemo.Styles.Add(clGray, clWhite, [fsBold]);
  GLSLMemo.AddWord(No, GLSLBuildIn);

  No := GLSLMemo.Styles.Add(clGreen, clWhite, [fsItalic]);
  GLSLMemo.AddWord(No, GLSLFunctions);

  No := GLSLMemo.Styles.Add(clYellow, clSilver, [fsItalic]);
  GLSLMemo.AddWord(No, GLSLFunctions);

  FLightLineStyle := GLSLMemo.Styles.Add(clBlack, clLtGray, []);

  GLSLMemo.MultiCommentLeft := '/*';
  GLSLMemo.MultiCommentRight := '*/';
  GLSLMemo.LineComment := '//';

  GLSLMemo.CaseSensitive := True;
  GLSLMemo.DelErase := True;

  item := NewItem('Attribute block', 0, False, True, OnTemplateClick, 0, '');
  item.Tag := 5;
  GLSL120.Add(item);

  item := NewItem('Basic vertex program', 0, False, True, OnTemplateClick, 0, '');
  item.Tag := 0;
  GLSL120.Add(item);

  item := NewItem('Basic vertex program with TBN pass', 0, False, True, OnTemplateClick, 0, '');
  item.Tag := 1;
  GLSL120.Add(item);

  item := NewItem('Basic fragment program, Phong lighting', 0, False, True, OnTemplateClick, 0, '');
  item.Tag := 2;
  GLSL120.Add(item);

  item := NewItem('Fragment program, normal mapping', 0, False, True, OnTemplateClick, 0, '');
  item.Tag := 3;
  GLSL120.Add(item);

  item := NewItem('Attribute block', 0, False, True, OnTemplateClick, 0, '');
  item.Tag := 6;
  GLSL330.Add(item);

  item := NewItem('Geometry program, edge detection', 0, False, True, OnTemplateClick, 0, '');
  item.Tag := 4;
  GLSL330.Add(item);
end;

procedure TShaderMemoForm.FormDestroy(Sender: TObject);
var
  reg: TRegistry;
begin
  reg := TRegistry.Create;
  try
    if reg.OpenKey(cRegistryKey, True) then
    begin
      reg.WriteInteger('Left', Left);
      reg.WriteInteger('Top', Top);
      reg.WriteInteger('Width', Width);
      reg.WriteInteger('Height', Height);
    end;
  finally
    reg.Free;
  end;
end;

procedure TShaderMemoForm.FormShow(Sender: TObject);
begin
  if GLSLMemo.Lines.Count = 0 then
    GLSLMemo.Lines.Add('');
end;

procedure TShaderMemoForm.GLSLMemoGutterClick(Sender: TObject; LineNo: Integer);
begin
  with GLSLMemo do
  begin
    LineStyle[LineNo] := FLightLineStyle - LineStyle[LineNo];
  end;
end;

procedure TShaderMemoForm.GLSLMemoGutterDraw(Sender: TObject; ACanvas: TCanvas;
  LineNo: Integer; rct: TRect);
var
  txt: string;
begin
  if GLSLMemo.LineStyle[LineNo] = FLightLineStyle then
    with rct, ACanvas do
    begin
      Pen.Color := GLSLMemo.GutterColor;
      Brush.Color := clWhite;
      Font.Style := Font.Style + [fsBold];
      txt := IntToStr(LineNo+1);
      TextRect(rct, txt, [tfCenter]);
    end;
end;

procedure TShaderMemoForm.GLSLMemoUndoChange(Sender: TObject; CanUndo, CanRedo: Boolean);
begin
  TBUndo.Enabled := CanUndo;
  TBRedo.Enabled := CanRedo;
end;

procedure TShaderMemoForm.OnTemplateClick(Sender: TObject);
begin
  GLSLMemo.InsertTemplate(cTemplates[TMenuItem(Sender).Tag]);
end;

procedure TShaderMemoForm.TBCommentClick(Sender: TObject);
var
  I: Integer;
  S, E: Integer;
begin
  with TFriendlyMemo(GLSLMemo) do
  begin
    if SelLength > 0 then
    begin
      S := SelStart.Y;
      E := SelEnd.Y;
      for I := S to E do
      begin
        SetCursor(0, I);
        InsertChar('/');
        InsertChar('/');
      end;
      SelectLines(S, E);
    end;
  end;
end;

procedure TShaderMemoForm.TBUncomentClick(Sender: TObject);
var
  I: Integer;
  S, E: Integer;
  C: string;
begin
  with TFriendlyMemo(GLSLMemo) do
  begin
    if SelLength > 0 then
    begin
      S := SelStart.Y;
      E := SelEnd.Y;
      for I := S to E do
      begin
        C := Lines[I];
        if (C[1] = '/') and (C[2] = '/') then
        begin
          Delete(C, 1, 2);
          Lines[I] := C;
          LineStyle[I] := 0;
        end;
      end;
      SelectLines(S, E);
    end;
  end;
end;

procedure TShaderMemoForm.TBCopyClick(Sender: TObject);
begin
  GLSLMemo.CopyToClipBoard;
end;

procedure TShaderMemoForm.TBCutClick(Sender: TObject);
begin
  GLSLMemo.CutToClipBoard;
end;

procedure TShaderMemoForm.TBIncIndentClick(Sender: TObject);
var
  I: Integer;
  S, E: Integer;
begin
  with TFriendlyMemo(GLSLMemo) do
  begin
    if SelLength > 0 then
    begin
      S := SelStart.Y;
      E := SelEnd.Y;
      for I := S to E do
      begin
        SetCursor(0, I);
        InsertChar(#9);
      end;
      SelectLines(S, E);
    end;
  end;
end;

procedure TShaderMemoForm.TBDecIndentClick(Sender: TObject);
var
  I, J: Integer;
  S, E: Integer;
  C: string;
begin
  with TFriendlyMemo(GLSLMemo) do
  begin
    if SelLength > 0 then
    begin
      S := SelStart.Y;
      E := SelEnd.Y;
      for I := S to E do
      begin
        C := Lines[I];
        for J := 1 to TabSize do
          if C[1] = ' ' then
            Delete(C, 1, 1);
        Lines[I] := C;
      end;
      SelectLines(S, E);
    end;
  end;
end;

procedure TShaderMemoForm.TBOpenClick(Sender: TObject);
begin
  if OpenDialog.Execute then
    GLSLMemo.Lines.LoadFromFile(OpenDialog.FileName);
  SetFocus;
end;

procedure TShaderMemoForm.TBPasteClick(Sender: TObject);
begin
  GLSLMemo.PasteFromClipBoard;
end;

procedure TShaderMemoForm.TBRedoClick(Sender: TObject);
begin
  with GLSLMemo do
  begin
    Redo;
    SetFocus;
  end;
end;

procedure TShaderMemoForm.TBSaveClick(Sender: TObject);
begin
  if SaveDialog.Execute then
    GLSLMemo.Lines.SaveToFile(SaveDialog.FileName);
  SetFocus;
end;

procedure TShaderMemoForm.TBStayOnTopClick(Sender: TObject);
begin
  if TBStayOnTop.Down then
    FormStyle := fsStayOnTop
  else
    FormStyle := fsNormal;
end;

procedure TShaderMemoForm.TBUndoClick(Sender: TObject);
begin
  with GLSLMemo do
  begin
    Undo;
    SetFocus;
  end;
end;

procedure TShaderMemoForm.CheckButtonClick(Sender: TObject);
begin
  if Assigned(FOnCheck) then
    FOnCheck(Self);
end;

initialization

finalization
  ReleaseGLShaderEditor;

end.

