From 0e7d435f13e84f33c0fdd7942f3d0a5bc82a776c Mon Sep 17 00:00:00 2001 From: Sylvain Becker Date: Tue, 19 Jan 2021 10:40:42 +0100 Subject: [PATCH] Add basic testgles2_sdf program to demonstrate sign distance field with opengles2 --- test/Makefile.in | 3 + test/testgles2_sdf.c | 802 ++++++++++++++++++++++++++++++ test/testgles2_sdf_img_normal.bmp | Bin 0 -> 68122 bytes test/testgles2_sdf_img_sdf.bmp | Bin 0 -> 72202 bytes 4 files changed, 805 insertions(+) create mode 100644 test/testgles2_sdf.c create mode 100644 test/testgles2_sdf_img_normal.bmp create mode 100644 test/testgles2_sdf_img_sdf.bmp diff --git a/test/Makefile.in b/test/Makefile.in index 8c3bbf2a14..6cf97ed2c2 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -170,6 +170,9 @@ testgles$(EXE): $(srcdir)/testgles.c testgles2$(EXE): $(srcdir)/testgles2.c $(CC) -o $@ $^ $(CFLAGS) $(LIBS) @MATHLIB@ +testgles2_sdf$(EXE): $(srcdir)/testgles2_sdf.c + $(CC) -o $@ $^ $(CFLAGS) $(LIBS) @MATHLIB@ + testhaptic$(EXE): $(srcdir)/testhaptic.c $(CC) -o $@ $^ $(CFLAGS) $(LIBS) diff --git a/test/testgles2_sdf.c b/test/testgles2_sdf.c new file mode 100644 index 0000000000..880915e96d --- /dev/null +++ b/test/testgles2_sdf.c @@ -0,0 +1,802 @@ +/* + Copyright (C) 1997-2021 Sam Lantinga + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely. +*/ +#include +#include +#include +#include + +#ifdef __EMSCRIPTEN__ +#include +#endif + +#include "SDL_test_common.h" + +#if defined(__IPHONEOS__) || defined(__ANDROID__) || defined(__EMSCRIPTEN__) || defined(__NACL__) \ + || defined(__WINDOWS__) || defined(__LINUX__) +#define HAVE_OPENGLES2 +#endif + +#ifdef HAVE_OPENGLES2 + +#include "SDL_opengles2.h" + +typedef struct GLES2_Context +{ +#define SDL_PROC(ret,func,params) ret (APIENTRY *func) params; +#include "../src/render/opengles2/SDL_gles2funcs.h" +#undef SDL_PROC +} GLES2_Context; + + +static SDL_Surface *g_surf_sdf = NULL; +GLenum g_texture; +GLenum g_texture_type = GL_TEXTURE_2D; +GLfloat g_verts[24]; +typedef enum +{ + GLES2_ATTRIBUTE_POSITION = 0, + GLES2_ATTRIBUTE_TEXCOORD = 1, + GLES2_ATTRIBUTE_ANGLE = 2, + GLES2_ATTRIBUTE_CENTER = 3, +} GLES2_Attribute; + +typedef enum +{ + GLES2_UNIFORM_PROJECTION, + GLES2_UNIFORM_TEXTURE, + GLES2_UNIFORM_COLOR, +} GLES2_Uniform; + + +GLuint g_uniform_locations[16]; + + + +static SDLTest_CommonState *state; +static SDL_GLContext *context = NULL; +static int depth = 16; +static GLES2_Context ctx; + +static int LoadContext(GLES2_Context * data) +{ +#if SDL_VIDEO_DRIVER_UIKIT +#define __SDL_NOGETPROCADDR__ +#elif SDL_VIDEO_DRIVER_ANDROID +#define __SDL_NOGETPROCADDR__ +#elif SDL_VIDEO_DRIVER_PANDORA +#define __SDL_NOGETPROCADDR__ +#endif + +#if defined __SDL_NOGETPROCADDR__ +#define SDL_PROC(ret,func,params) data->func=func; +#else +#define SDL_PROC(ret,func,params) \ + do { \ + data->func = SDL_GL_GetProcAddress(#func); \ + if ( ! data->func ) { \ + return SDL_SetError("Couldn't load GLES2 function %s: %s", #func, SDL_GetError()); \ + } \ + } while ( 0 ); +#endif /* __SDL_NOGETPROCADDR__ */ + +#include "../src/render/opengles2/SDL_gles2funcs.h" +#undef SDL_PROC + return 0; +} + +/* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */ +static void +quit(int rc) +{ + int i; + + if (context != NULL) { + for (i = 0; i < state->num_windows; i++) { + if (context[i]) { + SDL_GL_DeleteContext(context[i]); + } + } + + SDL_free(context); + } + + SDLTest_CommonQuit(state); + exit(rc); +} + +#define GL_CHECK(x) \ + x; \ + { \ + GLenum glError = ctx.glGetError(); \ + if(glError != GL_NO_ERROR) { \ + SDL_Log("glGetError() = %i (0x%.8x) at line %i\n", glError, glError, __LINE__); \ + quit(1); \ + } \ + } + + +/* + * Create shader, load in source, compile, dump debug as necessary. + * + * shader: Pointer to return created shader ID. + * source: Passed-in shader source code. + * shader_type: Passed to GL, e.g. GL_VERTEX_SHADER. + */ +void +process_shader(GLuint *shader, const char * source, GLint shader_type) +{ + GLint status = GL_FALSE; + const char *shaders[1] = { NULL }; + char buffer[1024]; + GLsizei length; + + /* Create shader and load into GL. */ + *shader = GL_CHECK(ctx.glCreateShader(shader_type)); + + shaders[0] = source; + + GL_CHECK(ctx.glShaderSource(*shader, 1, shaders, NULL)); + + /* Clean up shader source. */ + shaders[0] = NULL; + + /* Try compiling the shader. */ + GL_CHECK(ctx.glCompileShader(*shader)); + GL_CHECK(ctx.glGetShaderiv(*shader, GL_COMPILE_STATUS, &status)); + + /* Dump debug info (source and log) if compilation failed. */ + if(status != GL_TRUE) { + ctx.glGetProgramInfoLog(*shader, sizeof(buffer), &length, &buffer[0]); + buffer[length] = '\0'; + SDL_Log("Shader compilation failed: %s", buffer);fflush(stderr); + quit(-1); + } +} + +/* Notes on a_angle: + * It is a vector containing sin and cos for rotation matrix + * To get correct rotation for most cases when a_angle is disabled cos + value is decremented by 1.0 to get proper output with 0.0 which is + default value +*/ +static const Uint8 GLES2_VertexSrc_Default_[] = " \ + uniform mat4 u_projection; \ + attribute vec2 a_position; \ + attribute vec2 a_texCoord; \ + attribute vec2 a_angle; \ + attribute vec2 a_center; \ + varying vec2 v_texCoord; \ + \ + void main() \ + { \ + float s = a_angle[0]; \ + float c = a_angle[1] + 1.0; \ + mat2 rotationMatrix = mat2(c, -s, s, c); \ + vec2 position = rotationMatrix * (a_position - a_center) + a_center; \ + v_texCoord = a_texCoord; \ + gl_Position = u_projection * vec4(position, 0.0, 1.0);\ + gl_PointSize = 1.0; \ + } \ +"; + +static const Uint8 GLES2_FragmentSrc_TextureABGRSrc_[] = " \ + precision mediump float; \ + uniform sampler2D u_texture; \ + uniform vec4 u_color; \ + varying vec2 v_texCoord; \ + \ + void main() \ + { \ + gl_FragColor = texture2D(u_texture, v_texCoord); \ + gl_FragColor *= u_color; \ + } \ +"; + +/* RGB to ABGR conversion */ +static const Uint8 GLES2_FragmentSrc_TextureABGRSrc_SDF[] = " \ + #extension GL_OES_standard_derivatives : enable\n\ + \ + precision mediump float; \ + uniform sampler2D u_texture; \ + uniform vec4 u_color; \ + varying vec2 v_texCoord; \ + \ + void main() \ + { \ + vec4 abgr = texture2D(u_texture, v_texCoord); \ +\ + float sigDist = abgr.a; \ + \ + float w = fwidth( sigDist );\ + float alpha = clamp(smoothstep(0.5 - w, 0.5 + w, sigDist), 0.0, 1.0); \ +\ + gl_FragColor = vec4(abgr.rgb, abgr.a * alpha); \ + gl_FragColor.rgb *= gl_FragColor.a; \ + gl_FragColor *= u_color; \ + } \ +"; + +/* RGB to ABGR conversion DEBUG */ +static const char *GLES2_FragmentSrc_TextureABGRSrc_SDF_dbg = " \ + #extension GL_OES_standard_derivatives : enable\n\ + \ + precision mediump float; \ + uniform sampler2D u_texture; \ + uniform vec4 u_color; \ + varying vec2 v_texCoord; \ + \ + void main() \ + { \ + vec4 abgr = texture2D(u_texture, v_texCoord); \ +\ + float a = abgr.a; \ + gl_FragColor = vec4(a, a, a, 1.0); \ + } \ +"; + + +static float g_val = 1.0f; +static int g_use_SDF = 1; +static int g_use_SDF_debug = 0; +static float g_angle = 0.0f; +static float matrix_mvp[4][4]; + + + + +typedef struct shader_data +{ + GLuint shader_program, shader_frag, shader_vert; + + GLint attr_position; + GLint attr_color, attr_mvp; + +} shader_data; + +static void +Render(unsigned int width, unsigned int height, shader_data* data) +{ + ctx.glViewport(0, 0, 640, 480); + + GL_CHECK(ctx.glClear(GL_COLOR_BUFFER_BIT)); + + GL_CHECK(ctx.glUniformMatrix4fv(g_uniform_locations[GLES2_UNIFORM_PROJECTION], 1, GL_FALSE, (const float *)matrix_mvp)); + GL_CHECK(ctx.glUniform4f(g_uniform_locations[GLES2_UNIFORM_COLOR], 1.0f, 1.0f, 1.0f, 1.0f)); + + float *verts = g_verts; + + GL_CHECK(ctx.glVertexAttribPointer(GLES2_ATTRIBUTE_ANGLE, 2, GL_FLOAT, GL_FALSE, 0, (const GLvoid *) (verts + 16))); + GL_CHECK(ctx.glVertexAttribPointer(GLES2_ATTRIBUTE_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 0, (const GLvoid *) (verts + 8))); + GL_CHECK(ctx.glVertexAttribPointer(GLES2_ATTRIBUTE_POSITION, 2, GL_FLOAT, GL_FALSE, 0, (const GLvoid *) verts)); + + GL_CHECK(ctx.glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); +} + + +void renderCopy_angle(float degree_angle) +{ + const float radian_angle = (float)(3.141592 * degree_angle) / 180.0; + const GLfloat s = (GLfloat) SDL_sin(radian_angle); + const GLfloat c = (GLfloat) SDL_cos(radian_angle) - 1.0f; + GLfloat *verts = g_verts + 16; + *(verts++) = s; + *(verts++) = c; + *(verts++) = s; + *(verts++) = c; + *(verts++) = s; + *(verts++) = c; + *(verts++) = s; + *(verts++) = c; +} + + +void renderCopy_position(SDL_Rect *srcrect, SDL_Rect *dstrect) +{ + GLfloat minx, miny, maxx, maxy; + GLfloat minu, maxu, minv, maxv; + GLfloat *verts = g_verts; + + minx = dstrect->x; + miny = dstrect->y; + maxx = dstrect->x + dstrect->w; + maxy = dstrect->y + dstrect->h; + + minu = (GLfloat) srcrect->x / g_surf_sdf->w; + maxu = (GLfloat) (srcrect->x + srcrect->w) / g_surf_sdf->w; + minv = (GLfloat) srcrect->y / g_surf_sdf->h; + maxv = (GLfloat) (srcrect->y + srcrect->h) / g_surf_sdf->h; + + *(verts++) = minx; + *(verts++) = miny; + *(verts++) = maxx; + *(verts++) = miny; + *(verts++) = minx; + *(verts++) = maxy; + *(verts++) = maxx; + *(verts++) = maxy; + + *(verts++) = minu; + *(verts++) = minv; + *(verts++) = maxu; + *(verts++) = minv; + *(verts++) = minu; + *(verts++) = maxv; + *(verts++) = maxu; + *(verts++) = maxv; +} + +int done; +Uint32 frames; +shader_data *datas; + +void loop() +{ + SDL_Event event; + int i; + int status; + + /* Check for events */ + ++frames; + while (SDL_PollEvent(&event) && !done) { + switch (event.type) { + case SDL_KEYDOWN: + { + const int sym = event.key.keysym.sym; + + if (sym == SDLK_TAB) { + SDL_Log("Tab"); + + + } + + + if (sym == SDLK_LEFT) g_val -= 0.05; + if (sym == SDLK_RIGHT) g_val += 0.05; + if (sym == SDLK_UP) g_angle -= 1; + if (sym == SDLK_DOWN) g_angle += 1; + + + break; + } + + case SDL_WINDOWEVENT: + switch (event.window.event) { + case SDL_WINDOWEVENT_RESIZED: + for (i = 0; i < state->num_windows; ++i) { + if (event.window.windowID == SDL_GetWindowID(state->windows[i])) { + int w, h; + status = SDL_GL_MakeCurrent(state->windows[i], context[i]); + if (status) { + SDL_Log("SDL_GL_MakeCurrent(): %s\n", SDL_GetError()); + break; + } + /* Change view port to the new window dimensions */ + SDL_GL_GetDrawableSize(state->windows[i], &w, &h); + ctx.glViewport(0, 0, w, h); + state->window_w = event.window.data1; + state->window_h = event.window.data2; + /* Update window content */ + Render(event.window.data1, event.window.data2, &datas[i]); + SDL_GL_SwapWindow(state->windows[i]); + break; + } + } + break; + } + } + SDLTest_CommonEvent(state, &event, &done); + } + + + matrix_mvp[3][0] = -1.0f; + matrix_mvp[3][3] = 1.0f; + + matrix_mvp[0][0] = 2.0f / 640.0; + matrix_mvp[1][1] = -2.0f / 480.0; + matrix_mvp[3][1] = 1.0f; + + if (0) + { + float *f = matrix_mvp; + SDL_Log("-----------------------------------"); + SDL_Log("[ %f, %f, %f, %f ]", *f++, *f++, *f++, *f++); + SDL_Log("[ %f, %f, %f, %f ]", *f++, *f++, *f++, *f++); + SDL_Log("[ %f, %f, %f, %f ]", *f++, *f++, *f++, *f++); + SDL_Log("[ %f, %f, %f, %f ]", *f++, *f++, *f++, *f++); + SDL_Log("-----------------------------------"); + } + + renderCopy_angle(g_angle); + + int w, h; + SDL_GL_GetDrawableSize(state->windows[0], &w, &h); + SDL_Rect rs, rd; + rs.x = 0; rs.y = 0; rs.w = g_surf_sdf->w; rs.h = g_surf_sdf->h; + rd.w = g_surf_sdf->w * g_val; rd.h = g_surf_sdf->h * g_val; + rd.x = (w - rd.w) / 2; rd.y = (h - rd.h) / 2; + renderCopy_position(&rs, &rd); + + + if (!done) { + for (i = 0; i < state->num_windows; ++i) { + status = SDL_GL_MakeCurrent(state->windows[i], context[i]); + if (status) { + SDL_Log("SDL_GL_MakeCurrent(): %s\n", SDL_GetError()); + + /* Continue for next window */ + continue; + } + Render(state->window_w, state->window_h, &datas[i]); + SDL_GL_SwapWindow(state->windows[i]); + } + } +#ifdef __EMSCRIPTEN__ + else { + emscripten_cancel_main_loop(); + } +#endif +} + +int +main(int argc, char *argv[]) +{ + int fsaa, accel; + int value; + int i; + SDL_DisplayMode mode; + Uint32 then, now; + int status; + shader_data *data; + + /* Initialize parameters */ + fsaa = 0; + accel = 0; + + /* Initialize test framework */ + state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO); + if (!state) { + return 1; + } + for (i = 1; i < argc;) { + int consumed; + + consumed = SDLTest_CommonArg(state, i); + if (consumed == 0) { + if (SDL_strcasecmp(argv[i], "--fsaa") == 0) { + ++fsaa; + consumed = 1; + } else if (SDL_strcasecmp(argv[i], "--accel") == 0) { + ++accel; + consumed = 1; + } else if (SDL_strcasecmp(argv[i], "--zdepth") == 0) { + i++; + if (!argv[i]) { + consumed = -1; + } else { + depth = SDL_atoi(argv[i]); + consumed = 1; + } + } else { + consumed = -1; + } + } + if (consumed < 0) { + static const char *options[] = { "[--fsaa]", "[--accel]", "[--zdepth %d]", NULL }; + SDLTest_CommonLogUsage(state, argv[0], options); + quit(1); + } + i += consumed; + } + + /* Set OpenGL parameters */ + state->window_flags |= SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; + state->gl_red_size = 5; + state->gl_green_size = 5; + state->gl_blue_size = 5; + state->gl_depth_size = depth; + state->gl_major_version = 2; + state->gl_minor_version = 0; + state->gl_profile_mask = SDL_GL_CONTEXT_PROFILE_ES; + + if (fsaa) { + state->gl_multisamplebuffers=1; + state->gl_multisamplesamples=fsaa; + } + if (accel) { + state->gl_accelerated=1; + } + if (!SDLTest_CommonInit(state)) { + quit(2); + return 0; + } + + context = (SDL_GLContext *)SDL_calloc(state->num_windows, sizeof(context)); + if (context == NULL) { + SDL_Log("Out of memory!\n"); + quit(2); + } + + /* Create OpenGL ES contexts */ + for (i = 0; i < state->num_windows; i++) { + context[i] = SDL_GL_CreateContext(state->windows[i]); + if (!context[i]) { + SDL_Log("SDL_GL_CreateContext(): %s\n", SDL_GetError()); + quit(2); + } + } + + /* Important: call this *after* creating the context */ + if (LoadContext(&ctx) < 0) { + SDL_Log("Could not load GLES2 functions\n"); + quit(2); + return 0; + } + + SDL_memset(matrix_mvp, 0, sizeof (matrix_mvp)); + + { + char *f; + g_use_SDF = 1; + g_use_SDF_debug = 0; + + if (g_use_SDF) { + f = "testgles2_sdf_img_sdf.bmp"; + } else { + f = "testgles2_sdf_img_normal.bmp"; + } + + SDL_Log("SDF is %s", g_use_SDF ? "enabled" : "disabled"); + + /* Load SDF BMP image */ +#if 1 + SDL_Surface *tmp = SDL_LoadBMP(f); + if (tmp == NULL) { + SDL_Log("missing image file: %s", f); + exit(-1); + } else { + SDL_Log("Load image file: %s", f); + } + +#else + /* Generate SDF image using SDL_ttf */ + + #include "SDL_ttf.h" + char *font_file = "./font/DroidSansFallback.ttf"; + char *str = "Abcde"; + SDL_Color color = { 0, 0,0, 255}; + + TTF_Init(); + TTF_Font *font = TTF_OpenFont(font_file, 72); + + if (font == NULL) { + SDL_Log("Cannot open font %s", font_file); + } + + TTF_SetFontSDF(font, g_use_SDF); + SDL_Surface *tmp = TTF_RenderUTF8_Blended(font, str, color); + + SDL_Log("err: %s", SDL_GetError()); + if (tmp == NULL) { + SDL_Log("can't render text"); + return -1; + } + + SDL_SaveBMP(tmp, f); + + TTF_CloseFont(font); + TTF_Quit(); +#endif + g_surf_sdf = SDL_ConvertSurfaceFormat(tmp, SDL_PIXELFORMAT_ABGR8888, 0); + + SDL_SetSurfaceBlendMode(g_surf_sdf, SDL_BLENDMODE_BLEND); + } + + + if (state->render_flags & SDL_RENDERER_PRESENTVSYNC) { + SDL_GL_SetSwapInterval(1); + } else { + SDL_GL_SetSwapInterval(0); + } + + SDL_GetCurrentDisplayMode(0, &mode); + SDL_Log("Screen bpp: %d\n", SDL_BITSPERPIXEL(mode.format)); + SDL_Log("\n"); + SDL_Log("Vendor : %s\n", ctx.glGetString(GL_VENDOR)); + SDL_Log("Renderer : %s\n", ctx.glGetString(GL_RENDERER)); + SDL_Log("Version : %s\n", ctx.glGetString(GL_VERSION)); + SDL_Log("Extensions : %s\n", ctx.glGetString(GL_EXTENSIONS)); + SDL_Log("\n"); + + status = SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &value); + if (!status) { + SDL_Log("SDL_GL_RED_SIZE: requested %d, got %d\n", 5, value); + } else { + SDL_Log( "Failed to get SDL_GL_RED_SIZE: %s\n", + SDL_GetError()); + } + status = SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &value); + if (!status) { + SDL_Log("SDL_GL_GREEN_SIZE: requested %d, got %d\n", 5, value); + } else { + SDL_Log( "Failed to get SDL_GL_GREEN_SIZE: %s\n", + SDL_GetError()); + } + status = SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &value); + if (!status) { + SDL_Log("SDL_GL_BLUE_SIZE: requested %d, got %d\n", 5, value); + } else { + SDL_Log( "Failed to get SDL_GL_BLUE_SIZE: %s\n", + SDL_GetError()); + } + status = SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &value); + if (!status) { + SDL_Log("SDL_GL_DEPTH_SIZE: requested %d, got %d\n", depth, value); + } else { + SDL_Log( "Failed to get SDL_GL_DEPTH_SIZE: %s\n", + SDL_GetError()); + } + if (fsaa) { + status = SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &value); + if (!status) { + SDL_Log("SDL_GL_MULTISAMPLEBUFFERS: requested 1, got %d\n", value); + } else { + SDL_Log( "Failed to get SDL_GL_MULTISAMPLEBUFFERS: %s\n", + SDL_GetError()); + } + status = SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &value); + if (!status) { + SDL_Log("SDL_GL_MULTISAMPLESAMPLES: requested %d, got %d\n", fsaa, + value); + } else { + SDL_Log( "Failed to get SDL_GL_MULTISAMPLESAMPLES: %s\n", + SDL_GetError()); + } + } + if (accel) { + status = SDL_GL_GetAttribute(SDL_GL_ACCELERATED_VISUAL, &value); + if (!status) { + SDL_Log("SDL_GL_ACCELERATED_VISUAL: requested 1, got %d\n", value); + } else { + SDL_Log( "Failed to get SDL_GL_ACCELERATED_VISUAL: %s\n", + SDL_GetError()); + } + } + + datas = (shader_data *)SDL_calloc(state->num_windows, sizeof(shader_data)); + + /* Set rendering settings for each context */ + for (i = 0; i < state->num_windows; ++i) { + + int w, h; + status = SDL_GL_MakeCurrent(state->windows[i], context[i]); + if (status) { + SDL_Log("SDL_GL_MakeCurrent(): %s\n", SDL_GetError()); + + /* Continue for next window */ + continue; + } + + { + int format = GL_RGBA; + int type = GL_UNSIGNED_BYTE; + + GL_CHECK(ctx.glGenTextures(1, &g_texture)); + + ctx.glActiveTexture(GL_TEXTURE0); + ctx.glPixelStorei(GL_PACK_ALIGNMENT, 1); + ctx.glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + ctx.glBindTexture(g_texture_type, g_texture); + + ctx.glTexParameteri(g_texture_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + ctx.glTexParameteri(g_texture_type, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + ctx.glTexParameteri(g_texture_type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + ctx.glTexParameteri(g_texture_type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + GL_CHECK(ctx.glTexImage2D(g_texture_type, 0, format, g_surf_sdf->w, g_surf_sdf->h, 0, format, type, NULL)); + GL_CHECK(ctx.glTexSubImage2D(g_texture_type, 0, 0 /* xoffset */, 0 /* yoffset */, g_surf_sdf->w, g_surf_sdf->h, format, type, g_surf_sdf->pixels)); + } + + + SDL_GL_GetDrawableSize(state->windows[i], &w, &h); + ctx.glViewport(0, 0, w, h); + + data = &datas[i]; + + /* Shader Initialization */ + process_shader(&data->shader_vert, GLES2_VertexSrc_Default_, GL_VERTEX_SHADER); + + if (g_use_SDF) { + if (g_use_SDF_debug == 0) { + process_shader(&data->shader_frag, GLES2_FragmentSrc_TextureABGRSrc_SDF, GL_FRAGMENT_SHADER); + } else { + process_shader(&data->shader_frag, GLES2_FragmentSrc_TextureABGRSrc_SDF_dbg, GL_FRAGMENT_SHADER); + } + } else { + process_shader(&data->shader_frag, GLES2_FragmentSrc_TextureABGRSrc_, GL_FRAGMENT_SHADER); + } + + /* Create shader_program (ready to attach shaders) */ + data->shader_program = GL_CHECK(ctx.glCreateProgram()); + + /* Attach shaders and link shader_program */ + GL_CHECK(ctx.glAttachShader(data->shader_program, data->shader_vert)); + GL_CHECK(ctx.glAttachShader(data->shader_program, data->shader_frag)); + GL_CHECK(ctx.glLinkProgram(data->shader_program)); + + ctx.glBindAttribLocation(data->shader_program, GLES2_ATTRIBUTE_POSITION, "a_position"); + ctx.glBindAttribLocation(data->shader_program, GLES2_ATTRIBUTE_TEXCOORD, "a_texCoord"); + ctx.glBindAttribLocation(data->shader_program, GLES2_ATTRIBUTE_ANGLE, "a_angle"); + ctx.glBindAttribLocation(data->shader_program, GLES2_ATTRIBUTE_CENTER, "a_center"); + + /* Predetermine locations of uniform variables */ + g_uniform_locations[GLES2_UNIFORM_PROJECTION] = ctx.glGetUniformLocation(data->shader_program, "u_projection"); + g_uniform_locations[GLES2_UNIFORM_TEXTURE] = ctx.glGetUniformLocation(data->shader_program, "u_texture"); + g_uniform_locations[GLES2_UNIFORM_COLOR] = ctx.glGetUniformLocation(data->shader_program, "u_color"); + + GL_CHECK(ctx.glUseProgram(data->shader_program)); + + ctx.glEnableVertexAttribArray((GLenum) GLES2_ATTRIBUTE_ANGLE); + ctx.glDisableVertexAttribArray((GLenum) GLES2_ATTRIBUTE_CENTER); + ctx.glEnableVertexAttribArray(GLES2_ATTRIBUTE_POSITION); + ctx.glEnableVertexAttribArray((GLenum) GLES2_ATTRIBUTE_TEXCOORD); + + + ctx.glUniform1i(g_uniform_locations[GLES2_UNIFORM_TEXTURE], 0); /* always texture unit 0. */ + ctx.glActiveTexture(GL_TEXTURE0); + ctx.glBindTexture(g_texture_type, g_texture); + GL_CHECK(ctx.glClearColor(1, 1, 1, 1)); + + // SDL_BLENDMODE_BLEND + GL_CHECK(ctx.glEnable(GL_BLEND)); + ctx.glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + ctx.glBlendEquationSeparate(GL_FUNC_ADD, GL_FUNC_ADD); + + + } + + /* Main render loop */ + frames = 0; + then = SDL_GetTicks(); + done = 0; + +#ifdef __EMSCRIPTEN__ + emscripten_set_main_loop(loop, 0, 1); +#else + while (!done) { + loop(); + } +#endif + + /* Print out some timing information */ + now = SDL_GetTicks(); + if (now > then) { + SDL_Log("%2.2f frames per second\n", + ((double) frames * 1000) / (now - then)); + } +#if !defined(__ANDROID__) && !defined(__NACL__) + quit(0); +#endif + return 0; +} + +#else /* HAVE_OPENGLES2 */ + +int +main(int argc, char *argv[]) +{ + SDL_Log("No OpenGL ES support on this system\n"); + return 1; +} + +#endif /* HAVE_OPENGLES2 */ + +/* vi: set ts=4 sw=4 expandtab: */ diff --git a/test/testgles2_sdf_img_normal.bmp b/test/testgles2_sdf_img_normal.bmp new file mode 100644 index 0000000000000000000000000000000000000000..1209e1b1df7105dad9e3e401b9dd9a0d255e4162 GIT binary patch literal 68122 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