Skip to content

Instantly share code, notes, and snippets.

@ponceto
Last active March 4, 2024 12:33
Show Gist options
  • Save ponceto/5dbe749013f39d00c00d2f61b9cc19dc to your computer and use it in GitHub Desktop.
Save ponceto/5dbe749013f39d00c00d2f61b9cc19dc to your computer and use it in GitHub Desktop.
Emscripten SDL example

Emscripten

An SDL example to demonstrate the power of Emscripten.

Compile and run for Linux

Compile:

make -f Makefile.linux

Execute:

./program.bin

Cleanup:

make -f Makefile.linux clean

Compile and run for WASM

Compile:

make -f Makefile.wasm

Execute:

emrun ./program.html

Cleanup:

make -f Makefile.wasm clean
#
# Makefile.linux - Copyright (c) 2024 - Olivier Poncet
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# ----------------------------------------------------------------------------
# global environment
# ----------------------------------------------------------------------------
FLAGS = -O2 -Wall
CC = gcc
CFLAGS = -std=c99 $(FLAGS)
CXX = g++
CXXFLAGS = -std=c++14 $(FLAGS)
CPPFLAGS = -I.
LD = g++
LDFLAGS = -L.
CP = cp
CPFLAGS = -f
RM = rm
RMFLAGS = -f
# ----------------------------------------------------------------------------
# default rules
# ----------------------------------------------------------------------------
.c.o :
$(CC) -c $(CFLAGS) $(CPPFLAGS) $<
.cc.o :
$(CXX) -c $(CXXFLAGS) $(CPPFLAGS) $<
# ----------------------------------------------------------------------------
# global targets
# ----------------------------------------------------------------------------
all : build
build : build_project
@echo "=== $@ ok ==="
clean : clean_project
@echo "=== $@ ok ==="
# ----------------------------------------------------------------------------
# project files
# ----------------------------------------------------------------------------
PROJECT_TARGET = program.bin
PROJECT_CLEANUP = \
program.bin \
program.data \
program.html \
program.wasm \
program.js \
$(NULL)
PROJECT_SRCS = \
program.cc \
$(NULL)
PROJECT_HDRS = \
program.h \
$(NULL)
PROJECT_OBJS = \
program.o \
$(NULL)
PROJECT_LIBS = \
-lSDL2_image \
-lSDL2 \
$(NULL)
# ----------------------------------------------------------------------------
# build project
# ----------------------------------------------------------------------------
build_project : $(PROJECT_TARGET)
$(PROJECT_TARGET) : $(PROJECT_OBJS)
$(LD) $(LDFLAGS) -o $(PROJECT_TARGET) $(PROJECT_OBJS) $(PROJECT_LIBS)
# ----------------------------------------------------------------------------
# clean project
# ----------------------------------------------------------------------------
clean_project :
$(RM) $(RMFLAGS) $(PROJECT_OBJS) $(PROJECT_CLEANUP)
# ----------------------------------------------------------------------------
# End-Of-File
# ----------------------------------------------------------------------------
#
# Makefile.wasm - Copyright (c) 2024 - Olivier Poncet
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# ----------------------------------------------------------------------------
# global environment
# ----------------------------------------------------------------------------
FLAGS = -O2 -Wall -sUSE_SDL=2 -sUSE_SDL_IMAGE=2
CC = emcc
CFLAGS = -std=c99 $(FLAGS)
CXX = em++
CXXFLAGS = -std=c++14 $(FLAGS)
CPPFLAGS = -I.
LD = em++
LDFLAGS = -L. --use-preload-plugins --preload-file sdl.png
CP = cp
CPFLAGS = -f
RM = rm
RMFLAGS = -f
# ----------------------------------------------------------------------------
# default rules
# ----------------------------------------------------------------------------
.c.o :
$(CC) -c $(CFLAGS) $(CPPFLAGS) $<
.cc.o :
$(CXX) -c $(CXXFLAGS) $(CPPFLAGS) $<
# ----------------------------------------------------------------------------
# global targets
# ----------------------------------------------------------------------------
all : build
build : build_project
@echo "=== $@ ok ==="
clean : clean_project
@echo "=== $@ ok ==="
# ----------------------------------------------------------------------------
# project files
# ----------------------------------------------------------------------------
PROJECT_TARGET = program.html
PROJECT_CLEANUP = \
program.bin \
program.data \
program.html \
program.wasm \
program.js \
$(NULL)
PROJECT_SRCS = \
program.cc \
$(NULL)
PROJECT_HDRS = \
program.h \
$(NULL)
PROJECT_OBJS = \
program.o \
$(NULL)
PROJECT_LIBS = \
-lSDL2_image \
-lSDL2 \
$(NULL)
# ----------------------------------------------------------------------------
# build project
# ----------------------------------------------------------------------------
build_project : $(PROJECT_TARGET)
$(PROJECT_TARGET) : $(PROJECT_OBJS)
$(LD) $(LDFLAGS) -o $(PROJECT_TARGET) $(PROJECT_OBJS) $(PROJECT_LIBS)
# ----------------------------------------------------------------------------
# clean project
# ----------------------------------------------------------------------------
clean_project :
$(RM) $(RMFLAGS) $(PROJECT_OBJS) $(PROJECT_CLEANUP)
# ----------------------------------------------------------------------------
# End-Of-File
# ----------------------------------------------------------------------------
/*
* program.cc - Copyright (c) 2024 - Olivier Poncet
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cstdint>
#include <string>
#include <vector>
#include <memory>
#include <iostream>
#include <stdexcept>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#include "program.h"
// ---------------------------------------------------------------------------
// <anonymous>::ApplicationRunner
// ---------------------------------------------------------------------------
namespace {
struct ApplicationRunner
{
#ifdef __EMSCRIPTEN__
static void main_loop(Application& application)
{
if(application.running()) {
application.loop();
}
else {
::emscripten_cancel_main_loop();
}
}
static void run(Application& application)
{
::emscripten_set_main_loop_arg(reinterpret_cast<em_arg_callback_func>(&main_loop), &application, 0, 1);
}
#else
static void run(Application& application)
{
while(application.running()) {
application.loop();
}
}
#endif
};
}
// ---------------------------------------------------------------------------
// Console
// ---------------------------------------------------------------------------
void Console::println(const std::string& string)
{
std::cout << string << std::endl;
}
void Console::errorln(const std::string& string)
{
std::cerr << string << std::endl;
}
// ---------------------------------------------------------------------------
// Program
// ---------------------------------------------------------------------------
void Program::run(const std::string& title, const int width, const int height)
{
const auto application(std::make_unique<HelloWorldApp>(title, width, height));
ApplicationRunner::run(*application);
}
// ---------------------------------------------------------------------------
// Application
// ---------------------------------------------------------------------------
Application::Application()
: _running(true)
{
sdl_init();
}
Application::~Application()
{
sdl_quit();
}
void Application::sdl_init()
{
const int rc = ::SDL_Init(SDL_INIT_VIDEO);
if(rc != 0) {
throw std::runtime_error("SDL_Init() has failed");
}
}
void Application::sdl_quit()
{
::SDL_Quit();
}
// ---------------------------------------------------------------------------
// HelloWorldApp
// ---------------------------------------------------------------------------
HelloWorldApp::HelloWorldApp(const std::string& title, const int width, const int height)
: Application()
, _window()
, _renderer()
, _surface()
, _texture()
{
sdl_create(title, width, height);
}
HelloWorldApp::~HelloWorldApp()
{
sdl_destroy();
}
void HelloWorldApp::loop()
{
auto on_quit = [&](const SDL_QuitEvent& event) -> void
{
_running = false;
};
auto on_keypress = [&](const SDL_KeyboardEvent& event) -> void
{
if(event.keysym.sym == SDLK_ESCAPE) {
_running = false;
}
};
auto on_keyrelease = [&](const SDL_KeyboardEvent& event) -> void
{
if(event.keysym.sym == SDLK_ESCAPE) {
_running = false;
}
};
auto poll_events = [&]() -> void
{
SDL_Event event;
while(::SDL_PollEvent(&event) != 0) {
switch(event.type) {
case SDL_QUIT:
on_quit(event.quit);
break;
case SDL_KEYDOWN:
on_keypress(event.key);
break;
case SDL_KEYUP:
on_keyrelease(event.key);
break;
default:
break;
}
}
};
auto render_frame = [&](SDL_Renderer* renderer, SDL_Texture* texture) -> void
{
::SDL_SetRenderDrawColor(renderer, 192, 192, 192, 255);
::SDL_RenderClear(renderer);
::SDL_RenderCopy(renderer, texture, nullptr, nullptr);
::SDL_RenderPresent(renderer);
};
auto process = [&]() -> void
{
poll_events();
render_frame(_renderer.get(), _texture.get());
};
return process();
}
void HelloWorldApp::sdl_create(const std::string& title, const int width, const int height)
{
auto create_window = [&]() -> void
{
const int win_x = SDL_WINDOWPOS_UNDEFINED;
const int win_y = SDL_WINDOWPOS_UNDEFINED;
const int win_w = (width < 0 ? 800 : width );
const int win_h = (height < 0 ? 600 : height);
const Uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
if(!_window) {
_window.reset(::SDL_CreateWindow(title.c_str(), win_x, win_y, win_w, win_h, flags));
}
if(!_window) {
throw std::runtime_error("SDL_CreateWindow() has failed");
}
};
auto create_renderer = [&]() -> void
{
const int index = -1;
const Uint32 flags = 0;
if(!_renderer) {
_renderer.reset(::SDL_CreateRenderer(_window.get(), index, flags));
}
if(!_renderer) {
throw std::runtime_error("SDL_CreateRenderer() has failed");
}
};
auto create_surface = [&]() -> void
{
if(!_surface) {
_surface.reset(::IMG_Load("sdl.png"));
}
if(!_surface) {
throw std::runtime_error("IMG_Load() has failed");
}
};
auto create_texture = [&]() -> void
{
if(!_texture) {
_texture.reset(::SDL_CreateTextureFromSurface(_renderer.get(), _surface.get()));
}
if(!_texture) {
throw std::runtime_error("SDL_CreateTextureFromSurface() has failed");
}
};
auto create = [&]() -> void
{
create_window();
create_renderer();
create_surface();
create_texture();
};
return create();
}
void HelloWorldApp::sdl_destroy()
{
auto destroy_texture = [&]() -> void
{
_texture.reset();
};
auto destroy_surface = [&]() -> void
{
_surface.reset();
};
auto destroy_renderer = [&]() -> void
{
_renderer.reset();
};
auto destroy_window = [&]() -> void
{
_window.reset();
};
auto destroy = [&]() -> void
{
destroy_texture();
destroy_surface();
destroy_renderer();
destroy_window();
};
return destroy();
}
// ---------------------------------------------------------------------------
// main
// ---------------------------------------------------------------------------
int main(int argc, char* argv[])
{
try {
Program::run("Hello World!", 800, 600);
}
catch(const std::exception& e) {
Console::errorln(e.what());
}
catch(...) {
Console::errorln("fatal error!");
}
return EXIT_SUCCESS;
}
// ---------------------------------------------------------------------------
// End-Of-File
// ---------------------------------------------------------------------------
/*
* program.h - Copyright (c) 2024 - Olivier Poncet
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __PROGRAM_H__
#define __PROGRAM_H__
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
// ---------------------------------------------------------------------------
// sdl::WindowDeleter
// ---------------------------------------------------------------------------
namespace sdl {
struct WindowDeleter
{
void operator()(SDL_Window* window) const
{
window = (::SDL_DestroyWindow(window), nullptr);
}
};
}
// ---------------------------------------------------------------------------
// sdl::RendererDeleter
// ---------------------------------------------------------------------------
namespace sdl {
struct RendererDeleter
{
void operator()(SDL_Renderer* renderer) const
{
renderer = (::SDL_DestroyRenderer(renderer), nullptr);
}
};
}
// ---------------------------------------------------------------------------
// sdl::SurfaceDeleter
// ---------------------------------------------------------------------------
namespace sdl {
struct SurfaceDeleter
{
void operator()(SDL_Surface* surface) const
{
surface = (::SDL_FreeSurface(surface), nullptr);
}
};
}
// ---------------------------------------------------------------------------
// sdl::TextureDeleter
// ---------------------------------------------------------------------------
namespace sdl {
struct TextureDeleter
{
void operator()(SDL_Texture* texture) const
{
texture = (::SDL_DestroyTexture(texture), nullptr);
}
};
}
// ---------------------------------------------------------------------------
// sdl::declarations
// ---------------------------------------------------------------------------
namespace sdl {
using WindowPtr = std::unique_ptr<SDL_Window, WindowDeleter>;
using RendererPtr = std::unique_ptr<SDL_Renderer, RendererDeleter>;
using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
using TexturePtr = std::unique_ptr<SDL_Texture, TextureDeleter>;
}
// ---------------------------------------------------------------------------
// Console
// ---------------------------------------------------------------------------
struct Console
{
static void println(const std::string& string);
static void errorln(const std::string& string);
};
// ---------------------------------------------------------------------------
// Program
// ---------------------------------------------------------------------------
struct Program
{
static void run(const std::string& title, const int width, const int height);
};
// ---------------------------------------------------------------------------
// Application
// ---------------------------------------------------------------------------
class Application
{
public: // public interface
Application();
Application(const Application&) = delete;
Application& operator=(const Application&) = delete;
virtual ~Application();
virtual void loop() = 0;
bool running() const
{
return _running;
}
protected: // protected interface
void sdl_init();
void sdl_quit();
protected: // protected data
bool _running;
};
// ---------------------------------------------------------------------------
// HelloWorldApp
// ---------------------------------------------------------------------------
class HelloWorldApp final
: public Application
{
public: // public interface
HelloWorldApp(const std::string& title, const int width, const int height);
HelloWorldApp(const HelloWorldApp&) = delete;
HelloWorldApp& operator=(const HelloWorldApp&) = delete;
virtual ~HelloWorldApp();
virtual void loop() override final;
protected: // protected interface
void sdl_create(const std::string& title, const int width, const int height);
void sdl_destroy();
protected: // protected data
sdl::WindowPtr _window;
sdl::RendererPtr _renderer;
sdl::SurfacePtr _surface;
sdl::TexturePtr _texture;
};
// ---------------------------------------------------------------------------
// End-Of-File
// ---------------------------------------------------------------------------
#endif /* __PROGRAM_H__ */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment