source: trunk/src/gpu/shader.cpp @ 1212

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

build: allow to build the Direct3D 9 driver with the mingw compiler.

  • Property svn:keywords set to Id
File size: 14.9 KB
Line 
1//
2// Lol Engine
3//
4// Copyright: (c) 2010-2012 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#   if defined USE_D3D9
23#       include <algorithm>
24        using std::min;
25        using std::max;
26#       include <d3d9.h>
27#       include <d3dx9shader.h>
28#   endif
29#endif
30
31#include "core.h"
32#include "lolgl.h"
33
34using namespace std;
35
36#if defined USE_D3D9
37extern IDirect3DDevice9 *g_d3ddevice;
38#elif defined _XBOX
39extern D3DDevice *g_d3ddevice;
40#endif
41
42namespace lol
43{
44
45/*
46 * Shader implementation class
47 */
48
49class ShaderData
50{
51    friend class Shader;
52
53private:
54#if defined USE_D3D9
55    IDirect3DVertexShader9 *vert_shader;
56    IDirect3DPixelShader9 *frag_shader;
57    ID3DXConstantTable *vert_table, *frag_table;
58#elif defined _XBOX
59    D3DVertexShader *vert_shader;
60    D3DPixelShader *frag_shader;
61    ID3DXConstantTable *vert_table, *frag_table;
62#elif !defined __CELLOS_LV2__
63    GLuint prog_id, vert_id, frag_id;
64#else
65    CGprogram vert_id, frag_id;
66#endif
67    uint32_t vert_crc, frag_crc;
68
69    /* Shader patcher */
70    static int GetVersion();
71    static void Patch(char *dst, char const *vert, char const *frag);
72
73    /* Global shader cache */
74    static Shader *shaders[];
75    static int nshaders;
76};
77
78Shader *ShaderData::shaders[256];
79int ShaderData::nshaders = 0;
80
81/*
82 * Public Shader class
83 */
84
85Shader *Shader::Create(char const *vert, char const *frag)
86{
87    uint32_t new_vert_crc = Hash::Crc32(vert);
88    uint32_t new_frag_crc = Hash::Crc32(frag);
89
90    for (int n = 0; n < ShaderData::nshaders; n++)
91    {
92        if (ShaderData::shaders[n]->data->vert_crc == new_vert_crc
93             && ShaderData::shaders[n]->data->frag_crc == new_frag_crc)
94            return ShaderData::shaders[n];
95    }
96
97    Shader *ret = new Shader(vert, frag);
98    ShaderData::shaders[ShaderData::nshaders] = ret;
99    ShaderData::nshaders++;
100    return ret;
101}
102
103void Shader::Destroy(Shader *shader)
104{
105    /* XXX: do nothing! the shader should remain in cache */
106    (void)shader;
107}
108
109Shader::Shader(char const *vert, char const *frag)
110  : data(new ShaderData())
111{
112#if defined USE_D3D9 || defined _XBOX
113    ID3DXBuffer *shader_code, *error_msg;
114    HRESULT hr;
115#elif !defined __CELLOS_LV2__
116    char buf[4096], errbuf[4096];
117    char const *shader = buf;
118    GLint status;
119    GLsizei len;
120#else
121    /* Initialise the runtime shader compiler. FIXME: this needs only
122     * to be done once. */
123    cgRTCgcInit();
124#endif
125
126    /* Compile vertex shader */
127    data->vert_crc = Hash::Crc32(vert);
128#if defined USE_D3D9 || defined _XBOX
129    hr = D3DXCompileShader(vert, (UINT)strlen(vert), NULL, NULL, "main",
130                           "vs_2_0", 0, &shader_code, &error_msg,
131                           &data->vert_table);
132    if (FAILED(hr))
133    {
134        Log::Error("failed to compile vertex shader: %s",
135                   error_msg ? error_msg->GetBufferPointer() : "error");
136        Log::Error("shader source:\n%s\n", vert);
137    }
138    g_d3ddevice->CreateVertexShader((DWORD *)shader_code->GetBufferPointer(),
139                                    &data->vert_shader);
140    shader_code->Release();
141#elif !defined __CELLOS_LV2__
142    ShaderData::Patch(buf, vert, NULL);
143    data->vert_id = glCreateShader(GL_VERTEX_SHADER);
144    glShaderSource(data->vert_id, 1, &shader, NULL);
145    glCompileShader(data->vert_id);
146
147    glGetShaderiv(data->vert_id, GL_COMPILE_STATUS, &status);
148    if (status != GL_TRUE)
149    {
150        glGetShaderInfoLog(data->vert_id, sizeof(errbuf), &len, errbuf);
151        Log::Error("failed to compile vertex shader: %s", errbuf);
152        Log::Error("shader source:\n%s\n", buf);
153    }
154#else
155    data->vert_id = cgCreateProgram(cgCreateContext(), CG_SOURCE, vert,
156                                    cgGLGetLatestProfile(CG_GL_VERTEX),
157                                    NULL, NULL);
158    if (data->vert_id == NULL)
159    {
160        Log::Error("failed to compile vertex shader");
161        Log::Error("shader source:\n%s\n", vert);
162    }
163#endif
164
165    /* Compile fragment shader */
166    data->frag_crc = Hash::Crc32(frag);
167#if defined USE_D3D9 || defined _XBOX
168    hr = D3DXCompileShader(frag, (UINT)strlen(frag), NULL, NULL, "main",
169                           "ps_2_0", 0, &shader_code, &error_msg,
170                           &data->frag_table);
171    if (FAILED(hr))
172    {
173        Log::Error("failed to compile fragment shader: %s",
174                   error_msg ? error_msg->GetBufferPointer() : "error");
175        Log::Error("shader source:\n%s\n", frag);
176    }
177    g_d3ddevice->CreatePixelShader((DWORD *)shader_code->GetBufferPointer(),
178                                   &data->frag_shader);
179    shader_code->Release();
180#elif !defined __CELLOS_LV2__
181    ShaderData::Patch(buf, NULL, frag);
182    data->frag_id = glCreateShader(GL_FRAGMENT_SHADER);
183    glShaderSource(data->frag_id, 1, &shader, NULL);
184    glCompileShader(data->frag_id);
185
186    glGetShaderiv(data->frag_id, GL_COMPILE_STATUS, &status);
187    if (status != GL_TRUE)
188    {
189        glGetShaderInfoLog(data->frag_id, sizeof(errbuf), &len, errbuf);
190        Log::Error("failed to compile fragment shader: %s", errbuf);
191        Log::Error("shader source:\n%s\n", buf);
192    }
193#else
194    data->frag_id = cgCreateProgram(cgCreateContext(), CG_SOURCE, frag,
195                                    cgGLGetLatestProfile(CG_GL_FRAGMENT),
196                                    NULL, NULL);
197    if (data->frag_id == NULL)
198    {
199        Log::Error("failed to compile fragment shader");
200        Log::Error("shader source:\n%s\n", frag);
201    }
202#endif
203
204#if !defined USE_D3D9 && !defined _XBOX && !defined __CELLOS_LV2__
205    /* Create program */
206    data->prog_id = glCreateProgram();
207    glAttachShader(data->prog_id, data->vert_id);
208    glAttachShader(data->prog_id, data->frag_id);
209
210    glLinkProgram(data->prog_id);
211    glValidateProgram(data->prog_id);
212#endif
213}
214
215int Shader::GetAttribLocation(char const *attr) const
216{
217#if defined USE_D3D9 || defined _XBOX
218    /* FIXME: do we have attribs? */
219    return 0;
220#elif !defined __CELLOS_LV2__
221    return glGetAttribLocation(data->prog_id, attr);
222#else
223    /* FIXME: need to differentiate between vertex and fragment program */
224    return 0;
225#endif
226}
227
228int Shader::GetUniformLocation(char const *uni) const
229{
230#if defined USE_D3D9 || defined _XBOX
231    UINT hr1 = (uintptr_t)data->frag_table->GetConstantByName(NULL, uni);
232    UINT hr2 = (uintptr_t)data->vert_table->GetConstantByName(NULL, uni);
233    return (int)(((uint32_t)hr1 << 16) | (uint32_t)hr2);
234#elif !defined __CELLOS_LV2__
235    return glGetUniformLocation(data->prog_id, uni);
236#else
237    /* FIXME: need to differentiate between vertex and fragment program,
238     * and replace the ugly cast. */
239    CGparameter tmp = cgGetNamedParameter(data->vert_id, uni);
240    if (tmp == NULL)
241        tmp = cgGetNamedParameter(data->frag_id, uni);
242    return (int)(intptr_t)tmp;
243#endif
244}
245
246void Shader::SetUniform(int uni, float f)
247{
248#if defined USE_D3D9 || defined _XBOX
249    SetUniform(uni, vec4(f, 0, 0, 0));
250#elif !defined __CELLOS_LV2__
251    glUniform1f(uni, f);
252#else
253    cgGLSetParameter1f((CGparameter)(intptr_t)uni, f);
254#endif
255}
256
257void Shader::SetUniform(int uni, vec2 const &v)
258{
259#if defined USE_D3D9 || defined _XBOX
260    SetUniform(uni, vec4(v, 0, 0));
261#elif !defined __CELLOS_LV2__
262    glUniform2f(uni, v.x, v.y);
263#else
264    cgGLSetParameter2f((CGparameter)(intptr_t)uni, v.x, v.y);
265#endif
266}
267
268void Shader::SetUniform(int uni, vec3 const &v)
269{
270#if defined USE_D3D9 || defined _XBOX
271    SetUniform(uni, vec4(v, 0));
272#elif !defined __CELLOS_LV2__
273    glUniform3f(uni, v.x, v.y, v.z);
274#else
275    cgGLSetParameter3f((CGparameter)(intptr_t)uni, v.x, v.y, v.z);
276#endif
277}
278
279void Shader::SetUniform(int uni, vec4 const &v)
280{
281    /* FIXME: use the array versions of these functions */
282#if defined USE_D3D9 || defined _XBOX
283    UINT hr1 = (uint32_t)uni >> 16;
284    UINT hr2 = (uint32_t)(uint16_t)uni;
285    g_d3ddevice->SetPixelShaderConstantF(hr1, &v[0], 1);
286    g_d3ddevice->SetVertexShaderConstantF(hr2, &v[0], 1);
287#elif !defined __CELLOS_LV2__
288    glUniform4f(uni, v.x, v.y, v.z, v.w);
289#else
290    cgGLSetParameter4f((CGparameter)(intptr_t)uni, v.x, v.y, v.z, v.w);
291#endif
292}
293
294void Shader::SetUniform(int uni, mat4 const &m)
295{
296#if defined USE_D3D9 || defined _XBOX
297    UINT hr1 = (uint32_t)uni >> 16;
298    UINT hr2 = (uint32_t)(uint16_t)uni;
299    g_d3ddevice->SetPixelShaderConstantF(hr1, &m[0][0], 4);
300    g_d3ddevice->SetVertexShaderConstantF(hr2, &m[0][0], 4);
301#elif !defined __CELLOS_LV2__
302    glUniformMatrix4fv(uni, 1, GL_FALSE, &m[0][0]);
303#else
304    cgGLSetMatrixParameterfc((CGparameter)(intptr_t)uni, &m[0][0]);
305#endif
306}
307
308void Shader::Bind() const
309{
310#if defined USE_D3D9 || defined _XBOX
311    HRESULT hr;
312    hr = g_d3ddevice->SetVertexShader(data->vert_shader);
313    hr = g_d3ddevice->SetPixelShader(data->frag_shader);
314#elif !defined __CELLOS_LV2__
315    glUseProgram(data->prog_id);
316#else
317    cgGLEnableProfile(cgGLGetLatestProfile(CG_GL_VERTEX));
318    cgGLBindProgram(data->vert_id);
319    cgGLEnableProfile(cgGLGetLatestProfile(CG_GL_FRAGMENT));
320    cgGLBindProgram(data->frag_id);
321#endif
322}
323
324Shader::~Shader()
325{
326#if defined USE_D3D9 || defined _XBOX
327    data->vert_shader->Release();
328    data->vert_table->Release();
329    data->frag_shader->Release();
330    data->frag_table->Release();
331#elif !defined __CELLOS_LV2__
332    glDetachShader(data->prog_id, data->vert_id);
333    glDetachShader(data->prog_id, data->frag_id);
334    glDeleteShader(data->vert_id);
335    glDeleteShader(data->frag_id);
336    glDeleteProgram(data->prog_id);
337#else
338    cgDestroyProgram(data->vert_id);
339    cgDestroyProgram(data->frag_id);
340#endif
341    delete data;
342}
343
344/* Try to detect shader compiler features */
345int ShaderData::GetVersion()
346{
347    static int version = 0;
348
349#if !defined USE_D3D9 && !defined _XBOX && !defined __CELLOS_LV2__
350    if (!version)
351    {
352        char buf[4096];
353        GLsizei len;
354
355        int id = glCreateShader(GL_VERTEX_SHADER);
356
357        /* Can we compile 1.30 shaders? */
358        char const *test130 =
359            "#version 130\n"
360            "void main() { gl_Position = vec4(0.0, 0.0, 0.0, 0.0); }";
361        glShaderSource(id, 1, &test130, NULL);
362        glCompileShader(id);
363        glGetShaderInfoLog(id, sizeof(buf), &len, buf);
364        if (len <= 0)
365            version = 130;
366
367        /* If not, can we compile 1.20 shaders? */
368        if (!version)
369        {
370            char const *test120 =
371                "#version 120\n"
372                "void main() { gl_Position = vec4(0.0, 0.0, 0.0, 0.0); }";
373            glShaderSource(id, 1, &test120, NULL);
374            glCompileShader(id);
375            glGetShaderInfoLog(id, sizeof(buf), &len, buf);
376            if (len <= 0)
377                version = 120;
378        }
379
380        /* Otherwise, assume we can compile 1.10 shaders. */
381        if (!version)
382            version = 110;
383
384        glDeleteShader(id);
385    }
386#endif
387
388    return version;
389}
390
391/* Simple shader source patching for old GLSL versions.
392 * If supported version is 1.30, do nothing.
393 * If supported version is 1.20:
394 *  - replace "#version 130" with "#version 120"
395 */
396void ShaderData::Patch(char *dst, char const *vert, char const *frag)
397{
398    int ver_driver = GetVersion();
399
400    strcpy(dst, vert ? vert : frag);
401    if (ver_driver >= 130)
402        return;
403
404    int ver_shader = 110;
405    char *parser = strstr(dst, "#version");
406    if (parser)
407        ver_shader = atoi(parser + strlen("#version"));
408
409    if (ver_shader > 120 && ver_driver <= 120)
410    {
411        char const *end = dst + strlen(dst) + 1;
412
413        /* Find main() */
414        parser = strstr(dst, "main");
415        if (!parser) return;
416        parser = strstr(parser, "(");
417        if (!parser) return;
418        parser = strstr(parser, ")");
419        if (!parser) return;
420        parser = strstr(parser, "{");
421        if (!parser) return;
422        char *main = parser + 1;
423
424        /* Perform main() replaces */
425        char const * const main_replaces[] =
426        {
427#if 0
428            "in vec2 in_Vertex;", "vec2 in_Vertex = gl_Vertex.xy;",
429            "in vec3 in_Vertex;", "vec3 in_Vertex = gl_Vertex.xyz;",
430            "in vec4 in_Vertex;", "vec4 in_Vertex = gl_Vertex.xyzw;",
431
432            "in vec2 in_Color;", "vec2 in_Color = gl_Color.xy;",
433            "in vec3 in_Color;", "vec3 in_Color = gl_Color.xyz;",
434            "in vec4 in_Color;", "vec4 in_Color = gl_Color.xyzw;",
435
436            "in vec2 in_MultiTexCoord0;",
437               "vec2 in_MultiTexCoord0 = gl_MultiTexCoord0.xy;",
438            "in vec2 in_MultiTexCoord1;",
439               "vec2 in_MultiTexCoord1 = gl_MultiTexCoord1.xy;",
440            "in vec2 in_MultiTexCoord2;",
441               "vec2 in_MultiTexCoord2 = gl_MultiTexCoord2.xy;",
442            "in vec2 in_MultiTexCoord3;",
443               "vec2 in_MultiTexCoord3 = gl_MultiTexCoord3.xy;",
444            "in vec2 in_MultiTexCoord4;",
445               "vec2 in_MultiTexCoord4 = gl_MultiTexCoord4.xy;",
446            "in vec2 in_MultiTexCoord5;",
447               "vec2 in_MultiTexCoord5 = gl_MultiTexCoord5.xy;",
448            "in vec2 in_MultiTexCoord6;",
449               "vec2 in_MultiTexCoord6 = gl_MultiTexCoord6.xy;",
450            "in vec2 in_MultiTexCoord7;",
451               "vec2 in_MultiTexCoord7 = gl_MultiTexCoord7.xy;",
452#endif
453
454            NULL
455        };
456
457        for (char const * const *rep = main_replaces; rep[0]; rep += 2)
458        {
459            char *match = strstr(dst, rep[0]);
460            if (match && match < main)
461            {
462                size_t l0 = strlen(rep[0]);
463                size_t l1 = strlen(rep[1]);
464                memmove(main + l1, main, end - main);
465                memcpy(main, rep[1], l1);
466                memset(match, ' ', l0);
467                main += l1;
468                end += l1;
469            }
470        }
471
472        /* Perform small replaces */
473        char const * const fast_replaces[] =
474        {
475            "#version 130", "#version 120",
476            "in vec2", vert ? "attribute vec2" : "varying vec2",
477            "in vec3", vert ? "attribute vec3" : "varying vec3",
478            "in vec4", vert ? "attribute vec4" : "varying vec4",
479            "in mat4", vert ? "attribute mat4" : "varying mat4",
480            "out vec2", "varying vec2",
481            "out vec3", "varying vec3",
482            "out vec4", "varying vec4",
483            "out mat4", "varying mat4",
484            NULL
485        };
486
487        for (char const * const *rep = fast_replaces; rep[0]; rep += 2)
488        {
489            char *match;
490            while ((match = strstr(dst, rep[0])))
491            {
492                size_t l0 = strlen(rep[0]);
493                size_t l1 = strlen(rep[1]);
494
495                if (l1 > l0)
496                    memmove(match + l1, match + l0, (end - match) - l0);
497                memcpy(match, rep[1], l1);
498                if (l1 < l0)
499                    memset(match + l0, ' ', l1 - l0);
500                end += l1 - l0;
501            }
502        }
503    }
504}
505
506} /* namespace lol */
507
Note: See TracBrowser for help on using the repository browser.