source: trunk/src/shader/shader.cpp @ 828

Last change on this file since 828 was 828, checked in by sam, 8 years ago

ps3: if the implementation supports Cg but not GLSL (for instance on the PS3),
use the system's Cg compiler.

File size: 10.6 KB
Line 
1//
2// Lol Engine
3//
4// Copyright: (c) 2010-2011 Sam Hocevar <sam@hocevar.net>
5//   This program is free software; you can redistribute it and/or
6//   modify it under the terms of the Do What The Fuck You Want To
7//   Public License, Version 2, as published by Sam Hocevar. See
8//   http://sam.zoy.org/projects/COPYING.WTFPL for more details.
9//
10
11#if defined HAVE_CONFIG_H
12#   include "config.h"
13#endif
14
15#include <cmath>
16#include <cstring>
17#include <cstdio>
18
19#ifdef WIN32
20#   define WIN32_LEAN_AND_MEAN
21#   include <windows.h>
22#endif
23
24#include "core.h"
25#include "lolgl.h"
26
27using namespace std;
28
29namespace lol
30{
31
32/*
33 * Shader implementation class
34 */
35
36class ShaderData
37{
38    friend class Shader;
39
40private:
41#if !defined __CELLOS_LV2__
42    GLuint prog_id, vert_id, frag_id;
43#else
44    CGprogram vert_id, frag_id;
45#endif
46    uint32_t vert_crc, frag_crc;
47
48    /* Shader patcher */
49    static int GetVersion();
50    static void Patch(char *dst, char const *vert, char const *frag);
51
52    /* Global shader cache */
53    static Shader *shaders[];
54    static int nshaders;
55};
56
57Shader *ShaderData::shaders[256];
58int ShaderData::nshaders = 0;
59
60/*
61 * Public Shader class
62 */
63
64Shader *Shader::Create(char const *vert, char const *frag)
65{
66    uint32_t new_vert_crc = Hash::Crc32(vert);
67    uint32_t new_frag_crc = Hash::Crc32(frag);
68
69    for (int n = 0; n < ShaderData::nshaders; n++)
70    {
71        if (ShaderData::shaders[n]->data->vert_crc == new_vert_crc
72             && ShaderData::shaders[n]->data->frag_crc == new_frag_crc)
73            return ShaderData::shaders[n];
74    }
75
76    Shader *ret = new Shader(vert, frag);
77    ShaderData::shaders[ShaderData::nshaders] = ret;
78    ShaderData::nshaders++;
79    return ret;
80}
81
82void Shader::Destroy(Shader *shader)
83{
84    /* XXX: do nothing! the shader should remain in cache */
85    (void)shader;
86}
87
88Shader::Shader(char const *vert, char const *frag)
89  : data(new ShaderData())
90{
91#if !defined __CELLOS_LV2__
92    char buf[4096], errbuf[4096];
93    char const *shader = buf;
94    GLint status;
95    GLsizei len;
96#else
97    /* Initialise the runtime shader compiler. FIXME: this needs only
98     * to be done once. */
99    cgRTCgcInit();
100#endif
101
102    /* Compile vertex shader */
103    data->vert_crc = Hash::Crc32(vert);
104#if !defined __CELLOS_LV2__
105    ShaderData::Patch(buf, vert, NULL);
106    data->vert_id = glCreateShader(GL_VERTEX_SHADER);
107    glShaderSource(data->vert_id, 1, &shader, NULL);
108    glCompileShader(data->vert_id);
109
110    glGetShaderiv(data->vert_id, GL_COMPILE_STATUS, &status);
111    if (status != GL_TRUE)
112    {
113        glGetShaderInfoLog(data->vert_id, sizeof(errbuf), &len, errbuf);
114        Log::Error("failed to compile vertex shader: %s", errbuf);
115        Log::Error("shader source:\n%s\n", buf);
116    }
117#else
118    data->vert_id = cgCreateProgram(cgCreateContext(), CG_SOURCE, vert,
119                                    cgGLGetLatestProfile(CG_GL_VERTEX),
120                                    NULL, NULL);
121    if (data->vert_id == NULL)
122    {
123        Log::Error("failed to compile vertex shader");
124        Log::Error("shader source:\n%s\n", vert);
125    }
126#endif
127
128    /* Compile fragment shader */
129    data->frag_crc = Hash::Crc32(frag);
130#if !defined __CELLOS_LV2__
131    ShaderData::Patch(buf, NULL, frag);
132    data->frag_id = glCreateShader(GL_FRAGMENT_SHADER);
133    glShaderSource(data->frag_id, 1, &shader, NULL);
134    glCompileShader(data->frag_id);
135
136    glGetShaderiv(data->frag_id, GL_COMPILE_STATUS, &status);
137    if (status != GL_TRUE)
138    {
139        glGetShaderInfoLog(data->frag_id, sizeof(errbuf), &len, errbuf);
140        Log::Error("failed to compile fragment shader: %s", errbuf);
141        Log::Error("shader source:\n%s\n", buf);
142    }
143#else
144    data->frag_id = cgCreateProgram(cgCreateContext(), CG_SOURCE, frag,
145                                    cgGLGetLatestProfile(CG_GL_FRAGMENT),
146                                    NULL, NULL);
147    if (data->frag_id == NULL)
148    {
149        Log::Error("failed to compile fragment shader");
150        Log::Error("shader source:\n%s\n", frag);
151    }
152#endif
153
154#if !defined __CELLOS_LV2__
155    /* Create program */
156    data->prog_id = glCreateProgram();
157    glAttachShader(data->prog_id, data->vert_id);
158    glAttachShader(data->prog_id, data->frag_id);
159
160    glLinkProgram(data->prog_id);
161    glValidateProgram(data->prog_id);
162#endif
163}
164
165int Shader::GetAttribLocation(char const *attr) const
166{
167#if !defined __CELLOS_LV2__
168    return glGetAttribLocation(data->prog_id, attr);
169#else
170    /* FIXME: need to differentiate between vertex and fragment program */
171    return 0;
172#endif
173}
174
175int Shader::GetUniformLocation(char const *uni) const
176{
177#if !defined __CELLOS_LV2__
178    return glGetUniformLocation(data->prog_id, uni);
179#else
180    /* FIXME: need to differentiate between vertex and fragment program,
181     * and replace the ugly cast. */
182    CGparameter tmp = cgGetNamedParameter(data->vert_id, uni);
183    if (tmp == NULL)
184        tmp = cgGetNamedParameter(data->frag_id, uni);
185    return (int)(intptr_t)tmp;
186#endif
187}
188
189void Shader::Bind() const
190{
191#if !defined __CELLOS_LV2__
192    glUseProgram(data->prog_id);
193#else
194    cgGLEnableProfile(cgGLGetLatestProfile(CG_GL_VERTEX));
195    cgGLBindProgram(data->vert_id);
196    cgGLEnableProfile(cgGLGetLatestProfile(CG_GL_FRAGMENT));
197    cgGLBindProgram(data->frag_id);
198#endif
199}
200
201Shader::~Shader()
202{
203#if !defined __CELLOS_LV2__
204    glDetachShader(data->prog_id, data->vert_id);
205    glDetachShader(data->prog_id, data->frag_id);
206    glDeleteShader(data->vert_id);
207    glDeleteShader(data->frag_id);
208    glDeleteProgram(data->prog_id);
209#else
210    cgDestroyProgram(data->vert_id);
211    cgDestroyProgram(data->frag_id);
212#endif
213    delete data;
214}
215
216/* Try to detect shader compiler features */
217int ShaderData::GetVersion()
218{
219    static int version = 0;
220
221#if !defined __CELLOS_LV2__
222    if (!version)
223    {
224        char buf[4096];
225        GLsizei len;
226
227        int id = glCreateShader(GL_VERTEX_SHADER);
228
229        /* Can we compile 1.30 shaders? */
230        char const *test130 =
231            "#version 130\n"
232            "void main() { gl_Position = vec4(0.0, 0.0, 0.0, 0.0); }";
233        glShaderSource(id, 1, &test130, NULL);
234        glCompileShader(id);
235        glGetShaderInfoLog(id, sizeof(buf), &len, buf);
236        if (len <= 0)
237            version = 130;
238
239        /* If not, can we compile 1.20 shaders? */
240        if (!version)
241        {
242            char const *test120 =
243                "#version 120\n"
244                "void main() { gl_Position = vec4(0.0, 0.0, 0.0, 0.0); }";
245            glShaderSource(id, 1, &test120, NULL);
246            glCompileShader(id);
247            glGetShaderInfoLog(id, sizeof(buf), &len, buf);
248            if (len <= 0)
249                version = 120;
250        }
251
252        /* Otherwise, assume we can compile 1.10 shaders. */
253        if (!version)
254            version = 110;
255
256        glDeleteShader(id);
257    }
258#endif
259
260    return version;
261}
262
263/* Simple shader source patching for old GLSL versions.
264 * If supported version is 1.30, do nothing.
265 * If supported version is 1.20:
266 *  - replace "#version 130" with "#version 120"
267 */
268void ShaderData::Patch(char *dst, char const *vert, char const *frag)
269{
270    int ver_driver = GetVersion();
271
272    strcpy(dst, vert ? vert : frag);
273    if (ver_driver >= 130)
274        return;
275
276    int ver_shader = 110;
277    char *parser = strstr(dst, "#version");
278    if (parser)
279        ver_shader = atoi(parser + strlen("#version"));
280
281    if (ver_shader > 120 && ver_driver <= 120)
282    {
283        char const *end = dst + strlen(dst) + 1;
284
285        /* Find main() */
286        parser = strstr(dst, "main");
287        if (!parser) return;
288        parser = strstr(parser, "(");
289        if (!parser) return;
290        parser = strstr(parser, ")");
291        if (!parser) return;
292        parser = strstr(parser, "{");
293        if (!parser) return;
294        char *main = parser + 1;
295
296        /* Perform main() replaces */
297        char const * const main_replaces[] =
298        {
299#if 0
300            "in vec2 in_Vertex;", "vec2 in_Vertex = gl_Vertex.xy;",
301            "in vec3 in_Vertex;", "vec3 in_Vertex = gl_Vertex.xyz;",
302            "in vec4 in_Vertex;", "vec4 in_Vertex = gl_Vertex.xyzw;",
303
304            "in vec2 in_Color;", "vec2 in_Color = gl_Color.xy;",
305            "in vec3 in_Color;", "vec3 in_Color = gl_Color.xyz;",
306            "in vec4 in_Color;", "vec4 in_Color = gl_Color.xyzw;",
307
308            "in vec2 in_MultiTexCoord0;",
309               "vec2 in_MultiTexCoord0 = gl_MultiTexCoord0.xy;",
310            "in vec2 in_MultiTexCoord1;",
311               "vec2 in_MultiTexCoord1 = gl_MultiTexCoord1.xy;",
312            "in vec2 in_MultiTexCoord2;",
313               "vec2 in_MultiTexCoord2 = gl_MultiTexCoord2.xy;",
314            "in vec2 in_MultiTexCoord3;",
315               "vec2 in_MultiTexCoord3 = gl_MultiTexCoord3.xy;",
316            "in vec2 in_MultiTexCoord4;",
317               "vec2 in_MultiTexCoord4 = gl_MultiTexCoord4.xy;",
318            "in vec2 in_MultiTexCoord5;",
319               "vec2 in_MultiTexCoord5 = gl_MultiTexCoord5.xy;",
320            "in vec2 in_MultiTexCoord6;",
321               "vec2 in_MultiTexCoord6 = gl_MultiTexCoord6.xy;",
322            "in vec2 in_MultiTexCoord7;",
323               "vec2 in_MultiTexCoord7 = gl_MultiTexCoord7.xy;",
324#endif
325
326            NULL
327        };
328
329        for (char const * const *rep = main_replaces; rep[0]; rep += 2)
330        {
331            char *match = strstr(dst, rep[0]);
332            if (match && match < main)
333            {
334                size_t l0 = strlen(rep[0]);
335                size_t l1 = strlen(rep[1]);
336                memmove(main + l1, main, end - main);
337                memcpy(main, rep[1], l1);
338                memset(match, ' ', l0);
339                main += l1;
340                end += l1;
341            }
342        }
343
344        /* Perform small replaces */
345        char const * const fast_replaces[] =
346        {
347            "#version 130", "#version 120",
348            "in vec2", vert ? "attribute vec2" : "varying vec2",
349            "in vec3", vert ? "attribute vec3" : "varying vec3",
350            "in vec4", vert ? "attribute vec4" : "varying vec4",
351            "in mat4", vert ? "attribute mat4" : "varying mat4",
352            "out vec2", "varying vec2",
353            "out vec3", "varying vec3",
354            "out vec4", "varying vec4",
355            "out mat4", "varying mat4",
356            NULL
357        };
358
359        for (char const * const *rep = fast_replaces; rep[0]; rep += 2)
360        {
361            char *match;
362            while ((match = strstr(dst, rep[0])))
363            {
364                size_t l0 = strlen(rep[0]);
365                size_t l1 = strlen(rep[1]);
366
367                if (l1 > l0)
368                    memmove(match + l1, match + l0, (end - match) - l0);
369                memcpy(match, rep[1], l1);
370                if (l1 < l0)
371                    memset(match + l0, ' ', l1 - l0);
372                end += l1 - l0;
373            }
374        }
375    }
376}
377
378} /* namespace lol */
379
Note: See TracBrowser for help on using the repository browser.