Skip to content

Instantly share code, notes, and snippets.

@mortennobel
Last active October 23, 2024 20:56
Show Gist options
  • Save mortennobel/0e9e90c9bbc61cc99d5c3e9c038d8115 to your computer and use it in GitHub Desktop.
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)
//
// 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
@deus369
Copy link

deus369 commented Oct 16, 2022

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