Last active
October 23, 2024 20:56
-
-
Save mortennobel/0e9e90c9bbc61cc99d5c3e9c038d8115 to your computer and use it in GitHub Desktop.
Single file OpenGL 3.3 / WebGL (using Emscripten) example with texture (SDL2 / SDL_Image 2)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Compile for emscripten using | |
// emcc -Iinclude SingleFileOpenGLTex.cpp \ | |
-O2 -std=c++14 -s TOTAL_MEMORY=33554432 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS='["png"]' --preload-file examples/data -s USE_SDL=2 -o html/SingleFileOpenGLTex.html | |
// where the following images must be located in a subfolder | |
// - examples/data/test.png | |
// - examples/data/cartman.png | |
// - examples/data/cube-negx.png | |
// - examples/data/cube-negz.png | |
// | |
// Tested against emscripten/1.37.14 | |
#include "SDL.h" | |
#include <SDL_image.h> | |
#ifdef EMSCRIPTEN | |
# include <GLES2/gl2.h> | |
# include "emscripten.h" | |
#else | |
// OSX only - change on other platforms | |
# include <OpenGL/gl3.h> | |
#endif | |
#include <iostream> | |
#include <fstream> | |
#include <vector> | |
static bool quitting = false; | |
static SDL_Window *window = NULL; | |
static SDL_GLContext gl_context; | |
GLuint vertexBuffer, vertexArrayObject, shaderProgram; | |
GLint positionAttribute, uvAttribute; | |
int textures[4]; | |
int invert_image(int width, int height, void *image_pixels) { | |
auto temp_row = std::unique_ptr<char>(new char[width]); | |
if (temp_row.get() == nullptr) { | |
SDL_SetError("Not enough memory for image inversion"); | |
return -1; | |
} | |
//if height is odd, don't need to swap middle row | |
int height_div_2 = height / 2; | |
for (int index = 0; index < height_div_2; index++) { | |
//uses string.h | |
memcpy((Uint8 *)temp_row.get(), | |
(Uint8 *)(image_pixels)+ | |
width * index, | |
width); | |
memcpy( | |
(Uint8 *)(image_pixels)+ | |
width * index, | |
(Uint8 *)(image_pixels)+ | |
width * (height - index - 1), | |
width); | |
memcpy( | |
(Uint8 *)(image_pixels)+ | |
width * (height - index - 1), | |
temp_row.get(), | |
width); | |
} | |
return 0; | |
} | |
int LoadGLTextures( const char* filename) | |
{ | |
/* Status indicator */ | |
int Status = false; | |
unsigned int texture; | |
/* Create storage space for the texture */ | |
SDL_Surface *TextureImage[1]; | |
std::ifstream input( filename, std::ios::binary ); | |
// copies all data into buffer | |
std::vector<char> buffer(( | |
std::istreambuf_iterator<char>(input)), | |
(std::istreambuf_iterator<char>())); | |
std::cout << filename <<" size "<< buffer.size()<<std::endl; | |
/* Load The Bitmap, Check For Errors, If Bitmap's Not Found Quit */ | |
if ( ( TextureImage[0] = IMG_Load( filename ) ) ) | |
{ | |
/* Set the status to true */ | |
Status = true; | |
/* Create The Texture */ | |
glGenTextures( 1, &texture ); | |
/* Typical Texture Generation Using Data From The Bitmap */ | |
glBindTexture( GL_TEXTURE_2D, texture ); | |
std::cout << "Loaded "<<TextureImage[0]->w<<" "<<TextureImage[0]->h<<std::endl; | |
// Enforce RGB/RGBA | |
int format; | |
SDL_Surface* formattedSurf; | |
if (TextureImage[0]->format->BytesPerPixel==3) { | |
formattedSurf = SDL_ConvertSurfaceFormat(TextureImage[0], | |
SDL_PIXELFORMAT_RGB24, | |
0); | |
format = GL_RGB; | |
} else { | |
formattedSurf = SDL_ConvertSurfaceFormat(TextureImage[0], | |
SDL_PIXELFORMAT_RGBA32, | |
0); | |
format = GL_RGBA; | |
} | |
invert_image(formattedSurf->w*formattedSurf->format->BytesPerPixel, formattedSurf->h, (char *) formattedSurf->pixels); | |
/* Generate The Texture */ | |
glTexImage2D( GL_TEXTURE_2D, 0, format, formattedSurf->w, | |
formattedSurf->h, 0, format, | |
GL_UNSIGNED_BYTE, formattedSurf->pixels ); | |
/* Linear Filtering */ | |
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); | |
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); | |
SDL_FreeSurface(formattedSurf); | |
SDL_FreeSurface( TextureImage[0] ); | |
} else { | |
std::cout << "Cannot load "<<filename <<std::endl; | |
} | |
return texture; | |
} | |
void loadBufferData(){ | |
// vertex position, uv | |
float vertexData[24] = { | |
-0.5, -0.5, 0.0, 1.0 , 0.0, 0.0, | |
-0.5, 0.5, 0.0, 1.0 , 0.0, 1.0, | |
0.5, 0.5, 0.0, 1.0 , 1.0, 1.0, | |
0.5, -0.5, 0.0, 1.0 , 1.0, 0.0, | |
}; | |
#ifndef EMSCRIPTEN | |
glGenVertexArrays(1, &vertexArrayObject); | |
glBindVertexArray(vertexArrayObject); | |
#endif | |
glGenBuffers(1, &vertexBuffer); | |
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); | |
glBufferData(GL_ARRAY_BUFFER, 32 * sizeof(float), vertexData, GL_STATIC_DRAW); | |
glEnableVertexAttribArray(positionAttribute); | |
glEnableVertexAttribArray(uvAttribute); | |
int vertexSize =sizeof(float)*6; | |
glVertexAttribPointer(positionAttribute, 4, GL_FLOAT, GL_FALSE,vertexSize , (const GLvoid *)0); | |
glVertexAttribPointer(uvAttribute , 2, GL_FLOAT, GL_FALSE, vertexSize, (const GLvoid *)(sizeof(float)*4)); | |
} | |
GLuint initShader(const char* vShader, const char* fShader, const char* outputAttributeName) { | |
struct Shader { | |
GLenum type; | |
const char* source; | |
} shaders[2] = { | |
{ GL_VERTEX_SHADER, vShader }, | |
{ GL_FRAGMENT_SHADER, fShader } | |
}; | |
GLuint program = glCreateProgram(); | |
for ( int i = 0; i < 2; ++i ) { | |
Shader& s = shaders[i]; | |
GLuint shader = glCreateShader( s.type ); | |
glShaderSource( shader, 1, (const GLchar**) &s.source, NULL ); | |
glCompileShader( shader ); | |
GLint compiled; | |
glGetShaderiv( shader, GL_COMPILE_STATUS, &compiled ); | |
if ( !compiled ) { | |
std::cerr << " failed to compile:" << std::endl; | |
GLint logSize; | |
glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &logSize ); | |
char* logMsg = new char[logSize]; | |
glGetShaderInfoLog( shader, logSize, NULL, logMsg ); | |
std::cerr << logMsg << std::endl; | |
delete [] logMsg; | |
exit( EXIT_FAILURE ); | |
} | |
glAttachShader( program, shader ); | |
} | |
/* Link output */ | |
#ifndef EMSCRIPTEN | |
glBindFragDataLocation(program, 0, outputAttributeName); | |
#endif | |
/* link and error check */ | |
glLinkProgram(program); | |
GLint linked; | |
glGetProgramiv( program, GL_LINK_STATUS, &linked ); | |
if ( !linked ) { | |
std::cerr << "Shader program failed to link" << std::endl; | |
GLint logSize; | |
glGetProgramiv( program, GL_INFO_LOG_LENGTH, &logSize); | |
char* logMsg = new char[logSize]; | |
glGetProgramInfoLog( program, logSize, NULL, logMsg ); | |
std::cerr << logMsg << std::endl; | |
delete [] logMsg; | |
exit( EXIT_FAILURE ); | |
} | |
/* use program object */ | |
glUseProgram(program); | |
return program; | |
} | |
void loadShader(){ | |
#ifdef EMSCRIPTEN | |
const char * vert = R"(#version 100 | |
uniform vec2 offset; | |
attribute vec4 position; | |
attribute vec2 uv; | |
varying vec2 vUV; | |
void main (void) | |
{ | |
vUV = uv; | |
gl_Position = position + vec4(offset,0.0,0.0); | |
})"; | |
#else | |
const char * vert = R"(#version 150 | |
uniform vec2 offset; | |
in vec4 position; | |
in vec2 uv; | |
out vec2 vUV; | |
void main (void) | |
{ | |
vUV = uv; | |
gl_Position = position + vec4(offset,0.0,0.0); | |
})"; | |
#endif | |
#ifdef EMSCRIPTEN | |
const char * frag = R"(#version 100 | |
precision mediump float; | |
varying vec2 vUV; | |
uniform sampler2D tex; | |
void main(void) | |
{ | |
gl_FragColor = texture2D(tex,vUV); | |
})"; | |
#else | |
const char * frag = R"(#version 150 | |
in vec2 vUV; | |
out vec4 fragColor; | |
uniform sampler2D tex; | |
void main(void) | |
{ | |
fragColor = texture(tex,vUV); | |
})"; | |
#endif | |
shaderProgram = initShader(vert, frag, "fragColor"); | |
uvAttribute = glGetAttribLocation(shaderProgram, "uv"); | |
if (uvAttribute < 0) { | |
std::cerr << "Shader did not contain the 'color' attribute." << std::endl; | |
} | |
positionAttribute = glGetAttribLocation(shaderProgram, "position"); | |
if (positionAttribute < 0) { | |
std::cerr << "Shader did not contain the 'position' attribute." << std::endl; | |
} | |
} | |
void setup() { | |
std::cout << "OpenGL version "<<glGetString(GL_VERSION)<<std::endl; | |
loadShader(); | |
loadBufferData(); | |
textures[0] = LoadGLTextures( "examples/data/test.png"); | |
textures[1] = LoadGLTextures( "examples/data/cartman.png"); | |
textures[2] = LoadGLTextures( "examples/data/cube-negx.png"); | |
textures[3] = LoadGLTextures( "examples/data/cube-negz.png"); | |
glActiveTexture(GL_TEXTURE0); | |
glBindTexture(GL_TEXTURE_2D, textures[0]); | |
glEnable(GL_DEPTH_TEST); | |
} | |
void render() { | |
SDL_GL_MakeCurrent(window, gl_context); | |
glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); | |
glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT ); | |
glUseProgram(shaderProgram); | |
glUniform1i(glGetUniformLocation(shaderProgram, "tex"), 0); | |
int textureId = 0; | |
for (int x = 0;x<2;x++){ | |
for (int y = 0;y<2;y++){ | |
glBindTexture(GL_TEXTURE_2D, textures[textureId]); | |
glUniform2f(glGetUniformLocation(shaderProgram, "offset"), -0.5+x, -0.5+y); | |
glDrawArrays(GL_TRIANGLE_FAN, 0, 4); | |
textureId++; | |
} | |
} | |
SDL_GL_SwapWindow(window); | |
} | |
void update(){ | |
SDL_Event event; | |
while( SDL_PollEvent(&event) ) { | |
if(event.type == SDL_QUIT) { | |
quitting = true; | |
} | |
} | |
render(); | |
}; | |
int main(int argc, char *argv[]) { | |
#ifdef EMSCRIPTEN | |
SDL_Renderer *renderer = NULL; | |
SDL_CreateWindowAndRenderer(512, 512, SDL_WINDOW_OPENGL, &window, &renderer); | |
#else | |
if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_EVENTS) != 0) { | |
SDL_Log("Failed to initialize SDL: %s", SDL_GetError()); | |
return 1; | |
} | |
// Use a core profile setup. | |
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); | |
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); | |
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); | |
window = SDL_CreateWindow("title", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 512, 512, SDL_WINDOW_OPENGL); | |
gl_context = SDL_GL_CreateContext(window); | |
#endif | |
setup(); | |
#ifdef EMSCRIPTEN | |
// register update as callback | |
emscripten_set_main_loop(update, 0, 1); | |
#else | |
while(!quitting) { | |
update(); | |
SDL_Delay(2); | |
} | |
SDL_GL_DeleteContext(gl_context); | |
SDL_DestroyWindow(window); | |
SDL_Quit(); | |
exit(0); | |
#endif | |
} //main |
I was also wondering... Wishing the SDL / EMCC documents were better done.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hello, thanks for your example. Do you know why you need to do
SDL_CreateWindowAndRenderer
instead ofSDL_CreateWindow
?