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 8c3bbf2a1..6cf97ed2c 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 000000000..880915e96 --- /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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 nfH7bU7z4(DF<=ZB1IBX8(fH7bUkb(aJ?X9&q literal 0 HcmV?d00001 diff --git a/test/testgles2_sdf_img_sdf.bmp b/test/testgles2_sdf_img_sdf.bmp new file mode 100644 index 0000000000000000000000000000000000000000..5f983d2d211509e9e91e4e65e173d78c1a9d64cb GIT binary patch literal 72202 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 literal 0 HcmV?d00001