examples: Added initial examples infrastructure.

This commit is contained in:
Ryan C. Gordon 2024-07-22 22:38:21 -04:00
parent 2f6e34d2d0
commit 5339b4458d
No known key found for this signature in database
GPG Key ID: FA148B892AB48044
6 changed files with 569 additions and 0 deletions

View File

@ -0,0 +1,183 @@
#!/usr/bin/perl -w
# Simple DirectMedia Layer
# Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
#
# 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, subject to the following restrictions:
#
# 1. The origin of this software must not be misrepresented; you must not
# claim that you wrote the original software. If you use this software
# in a product, an acknowledgment in the product documentation would be
# appreciated but is not required.
# 2. Altered source versions must be plainly marked as such, and must not be
# misrepresented as being the original software.
# 3. This notice may not be removed or altered from any source distribution.
use warnings;
use strict;
use File::Basename;
use Cwd qw(abs_path);
use IPC::Open2;
my $examples_dir = abs_path(dirname(__FILE__) . "/../examples");
my $project = undef;
my $emsdk_dir = undef;
my $compile_dir = undef;
my $output_dir = undef;
sub usage {
die("USAGE: $0 <project_name> <emsdk_dir> <compiler_output_directory> <html_output_directory>\n\n");
}
sub do_system {
my $cmd = shift;
$cmd = "exec /usr/bin/bash -c \"$cmd\"";
print("$cmd\n");
return system($cmd);
}
sub do_mkdir {
my $d = shift;
if ( ! -d $d ) {
print("mkdir '$d'\n");
mkdir($d) or die("Couldn't mkdir('$d'): $!\n");
}
}
sub build_latest {
# Try to build just the latest without re-running cmake, since that is SLOW.
print("Building latest version of $project ...\n");
if (do_system("EMSDK_QUIET=1 source '$emsdk_dir/emsdk_env.sh' && cd '$compile_dir' && ninja") != 0) {
# Build failed? Try nuking the build dir and running CMake from scratch.
print("\n\nBuilding latest version of $project FROM SCRATCH ...\n");
if (do_system("EMSDK_QUIET=1 source '$emsdk_dir/emsdk_env.sh' && rm -rf '$compile_dir' && mkdir '$compile_dir' && cd '$compile_dir' && emcmake cmake -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel .. && ninja") != 0) {
die("Failed to build latest version of $project!\n"); # oh well.
}
}
}
sub handle_example_dir {
my $category = shift;
my $example = shift;
my @files = ();
my $files_str = '';
opendir(my $dh, "$examples_dir/$category/$example") or die("Couldn't opendir '$examples_dir/$category/$example': $!\n");
my $spc = '';
while (readdir($dh)) {
next if not /\.c\Z/; # only care about .c files.
my $path = "$examples_dir/$category/$example/$_";
next if not -f $path; # only care about files.
push @files, $path;
$files_str .= "$spc$path";
$spc = ' ';
}
closedir($dh);
my $dst = "$output_dir/$category/$example";
do_mkdir($dst);
print("Building $category/$example ...\n");
# !!! FIXME: hardcoded SDL3 references, need to fix this for satellite libraries and SDL2.
do_system("EMSDK_QUIET=1 source '$emsdk_dir/emsdk_env.sh' && emcc -s USE_SDL=0 -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s MAXIMUM_MEMORY=1gb -s ASSERTIONS=0 -o '$dst/index.js' '-I$examples_dir/../include' $files_str '$compile_dir/libSDL3.a'") == 0
or die("Failed to build $category/$example!\n");
my $highlight_cmd = "highlight '--outdir=$dst' --style-outfile=highlight.css --fragment --stdout --syntax=c '--plug-in=$examples_dir/highlight-plugin.lua'";
print("$highlight_cmd\n");
my $pid = open2(my $child_out, my $child_in, $highlight_cmd);
my $htmlified_source_code = '';
foreach (@files) {
my $path = $_;
open my $srccode, '<', $path or die("Couldn't open '$path': $!\n");
my $fname = "$path";
$fname =~ s/\A.*\///;
print $child_in "/* $fname ... */\n\n";
while (<$srccode>) {
print $child_in $_;
}
close($srccode);
}
close($child_in);
while (<$child_out>) {
$htmlified_source_code .= $_;
}
close($child_out);
waitpid($pid, 0);
my $html = '';
open my $htmltemplate, '<', "$examples_dir/template.html" or die("Couldn't open '$examples_dir/template.html': $!\n");
while (<$htmltemplate>) {
s/\@project_name\@/$project/g;
s/\@category_name\@/$category/g;
s/\@example_name\@/$example/g;
s/\@htmlified_source_code\@/$htmlified_source_code/g;
$html .= $_;
}
close($htmltemplate);
open my $htmloutput, '>', "$dst/index.html" or die("Couldn't open '$dst/index.html': $!\n");
print $htmloutput $html;
close($htmloutput);
}
sub handle_category_dir {
my $category = shift;
do_mkdir("$output_dir/$category");
opendir(my $dh, "$examples_dir/$category") or die("Couldn't opendir '$examples_dir/$category': $!\n");
while (readdir($dh)) {
next if ($_ eq '.') || ($_ eq '..'); # obviously skip current and parent entries.
next if not -d "$examples_dir/$category/$_"; # only care about subdirectories.
handle_example_dir($category, $_);
}
closedir($dh);
}
# Mainline!
foreach (@ARGV) {
$project = $_, next if not defined $project;
$emsdk_dir = $_, next if not defined $emsdk_dir;
$compile_dir = $_, next if not defined $compile_dir;
$output_dir = $_, next if not defined $output_dir;
usage(); # too many arguments.
}
usage() if not defined $output_dir;
build_latest();
do_mkdir($output_dir);
print("Examples dir: $examples_dir\n");
opendir(my $dh, $examples_dir) or die("Couldn't opendir '$examples_dir': $!\n");
while (readdir($dh)) {
next if ($_ eq '.') || ($_ eq '..'); # obviously skip current and parent entries.
next if not -d "$examples_dir/$_"; # only care about subdirectories.
handle_category_dir($_);
}
closedir($dh);
exit(0); # success!

67
examples/README.md Normal file
View File

@ -0,0 +1,67 @@
# Examples
## What is this?
In here are a collection of standalone SDL application examples. Unless
otherwise stated, they should work on all supported platforms out of the box.
If they don't [please file a bug to let us know](https://github.com/libsdl-org/SDL/issues/new).
## What is this SDL_AppIterate thing?
SDL can optionally build apps as a collection of callbacks instead of the
usual program structure that starts and ends in a function called `main`.
The examples use this format for two reasons.
First, it allows the examples to work when built as web applications without
a pile of ugly `#ifdef`s, and all of these examples are published on the web
at [examples.libsdl.org](https://examples.libsdl.org/), so you can easily see
them in action.
Second, it's example code! The callbacks let us cleanly break the program up
into the four logical pieces most apps care about:
- Program startup
- Event handling
- What the program actually does in a single frame
- Program shutdown
A detailed technical explanation of these callbacks is in
docs/README-main-functions.md (or view that page on the web on
[the wiki](https://wiki.libsdl.org/SDL3/README/main-functions#main-callbacks-in-sdl3).
## I would like to build and run these examples myself.
When you build SDL with CMake, you can add `-DSDL_BUILD_EXAMPLES=On` to the
CMake command line. When you build SDL, these examples will be built with it.
But most of these can just be built as a single .c file, as long as you point
your compiler at SDL3's headers and link against SDL.
## What is the license on the example code? Can I paste this into my project?
All code in the examples directory is considered public domain! You can do
anything you like with it, including copy/paste it into your closed-source
project, sell it, and pretend you wrote it yourself. We do not require you to
give us credit for this code (but we always appreciate if you do!).
This is only true for the examples directory. The rest of SDL falls under the
[zlib license](https://github.com/libsdl-org/SDL/blob/main/LICENSE.txt).
## What is template.html and highlight-plugin.lua in this directory?
This is what [examples.libsdl.org](https://examples.libsdl.org/) uses when
generating the web versions of these example programs. You can ignore this,
unless you are improving it, in which case we definitely would love to hear
from you!
## What is template.c in this directory?
If writing new examples, this is the skeleton code we start from, to keep
everything consistent. You can ignore it.

View File

@ -0,0 +1,77 @@
-- This code adapted from https://gitlab.com/saalen/highlight/-/wikis/Plug-Ins
-- first add a description of what the plug-in does
Description="Add wiki.libsdl.org reference links to HTML, LaTeX or RTF output"
-- define the plugin categories (ie. supported output formats; languages)
Categories = { "c", "c++" }
-- the syntaxUpdate function contains code related to syntax recognition
function syntaxUpdate(desc)
-- if the current file is not C/C++ file we exit
if desc~="C and C++" then
return
end
-- this function returns a qt-project reference link of the given token
function getURL(token)
-- generate the URL
url='https://wiki.libsdl.org/SDL3/'.. token
-- embed the URL in a hyperlink according to the output format
-- first HTML, then LaTeX and RTF
if (HL_OUTPUT== HL_FORMAT_HTML or HL_OUTPUT == HL_FORMAT_XHTML) then
return '<a class="hl" target="new" href="'
.. url .. '">'.. token .. '</a>'
elseif (HL_OUTPUT == HL_FORMAT_LATEX) then
return '\\href{'..url..'}{'..token..'}'
elseif (HL_OUTPUT == HL_FORMAT_RTF) then
return '{{\\field{\\*\\fldinst HYPERLINK "'
..url..'" }{\\fldrslt\\ul\\ulc0 '..token..'}}}'
end
end
-- the Decorate function will be invoked for every recognized token
function Decorate(token, state)
-- we are only interested in keywords, preprocessor or default items
if (state ~= HL_STANDARD and state ~= HL_KEYWORD and
state ~=HL_PREPROC) then
return
end
-- SDL keywords start with SDL_
-- if this pattern applies to the token, we return the URL
-- if we return nothing, the token is outputted as is
if string.find(token, "SDL_")==1 then
return getURL(token)
end
end
end
-- the themeUpdate function contains code related to the theme
function themeUpdate(desc)
-- the Injections table can be used to add style information to the theme
-- HTML: we add additional CSS style information to beautify hyperlinks,
-- they should have the same color as their surrounding tags
if (HL_OUTPUT == HL_FORMAT_HTML or HL_OUTPUT == HL_FORMAT_XHTML) then
Injections[#Injections+1]=
"a.hl, a.hl:visited {color:inherit;font-weight:inherit;text-decoration:none}"
-- LaTeX: hyperlinks require the hyperref package, so we add this here
-- the colorlinks and pdfborderstyle options remove ugly boxes in the output
elseif (HL_OUTPUT==HL_FORMAT_LATEX) then
Injections[#Injections+1]=
"\\usepackage[colorlinks=false, pdfborderstyle={/S/U/W 1}]{hyperref}"
end
end
-- let highlight load the chunks
Plugins={
{ Type="lang", Chunk=syntaxUpdate },
{ Type="theme", Chunk=themeUpdate },
}

View File

@ -0,0 +1,80 @@
/*
* This example code creates an SDL window and renderer, and then clears the
* window to a different color every frame, so you'll effectively get a window
* that's smoothly fading between colors.
*
* This code is public domain. Feel free to use it for any purpose!
*/
#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
/* We will use this renderer to draw into this window every frame. */
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
/* the current red color we're clearing to. */
static Uint8 red = 0;
/* When fading up, this is 1, when fading down, it's -1. */
static int fade_direction = 1;
/* This function runs once at startup. */
int SDL_AppInit(void **appstate, int argc, char *argv[])
{
if (SDL_CreateWindowAndRenderer("examples/renderer/clear", 640, 480, 0, &window, &renderer) == -1) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't create window/renderer!", SDL_GetError(), NULL);
return SDL_APP_FAILURE;
}
SDL_SetRenderVSync(renderer, 1); /* try to show frames at the monitor refresh rate. */
return SDL_APP_CONTINUE; /* carry on with the program! */
}
/* This function runs when a new event (mouse input, keypresses, etc) occurs. */
int SDL_AppEvent(void *appstate, const SDL_Event *event)
{
if (event->type == SDL_EVENT_QUIT) {
return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */
}
return SDL_APP_CONTINUE; /* carry on with the program! */
}
/* This function runs once per frame, and is the heart of the program. */
int SDL_AppIterate(void *appstate)
{
/* since we're always fading red, we leave green and blue at zero.
alpha doesn't mean much here, so leave it at full (255, no transparency). */
SDL_SetRenderDrawColor(renderer, red, 0, 0, 255);
/* clear the window to the draw color. */
SDL_RenderClear(renderer);
/* put the newly-cleared rendering on the screen. */
SDL_RenderPresent(renderer);
/* update the color for the next frame we will draw. */
if (fade_direction > 0) {
if (red == 255) {
fade_direction = -1;
} else {
red++;
}
} else if (fade_direction < 0) {
if (red == 0) {
fade_direction = 1;
} else {
red--;
}
}
return SDL_APP_CONTINUE; /* carry on with the program! */
}
/* This function runs once at shutdown. */
void SDL_AppQuit(void *appstate)
{
/* SDL will clean up the window/renderer for us. */
}

45
examples/template.c Normal file
View File

@ -0,0 +1,45 @@
/*
* This example code $WHAT_IT_DOES.
*
* This code is public domain. Feel free to use it for any purpose!
*/
#define SDL_MAIN_USE_CALLBACKS 1 /* use the callbacks instead of main() */
#include <SDL3/SDL.h>
#include <SDL3/SDL_main.h>
/* We will use this renderer to draw into this window every frame. */
static SDL_Window *window = NULL;
static SDL_Renderer *renderer = NULL;
/* This function runs once at startup. */
int SDL_AppInit(void **appstate, int argc, char *argv[])
{
if (SDL_CreateWindowAndRenderer("examples/renderer/clear", 640, 480, 0, &window, &renderer) == -1) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Couldn't create window/renderer!", SDL_GetError(), NULL);
return SDL_APP_FAILURE;
}
return SDL_APP_CONTINUE; /* carry on with the program! */
}
/* This function runs when a new event (mouse input, keypresses, etc) occurs. */
int SDL_AppEvent(void *appstate, const SDL_Event *event)
{
if (event->type == SDL_EVENT_QUIT) {
return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */
}
return SDL_APP_CONTINUE; /* carry on with the program! */
}
/* This function runs once per frame, and is the heart of the program. */
int SDL_AppIterate(void *appstate)
{
}
/* This function runs once at shutdown. */
void SDL_AppQuit(void *appstate)
{
/* SDL will clean up the window/renderer for us. */
}

117
examples/template.html Normal file
View File

@ -0,0 +1,117 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>@project_name@ Example: @category_name@/@example_name@</title>
<style>
body {
font-family: arial;
margin: 0;
padding: none;
}
.emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
div.emscripten { text-align: center; }
div.emscripten_border { border: 1px solid black; }
/* the canvas *must not* have any border or padding, or mouse coords will be wrong */
canvas.emscripten { border: 0px none; background-color: black; }
@-webkit-keyframes rotation {
from {-webkit-transform: rotate(0deg);}
to {-webkit-transform: rotate(360deg);}
}
@-moz-keyframes rotation {
from {-moz-transform: rotate(0deg);}
to {-moz-transform: rotate(360deg);}
}
@-o-keyframes rotation {
from {-o-transform: rotate(0deg);}
to {-o-transform: rotate(360deg);}
}
@keyframes rotation {
from {transform: rotate(0deg);}
to {transform: rotate(360deg);}
}
#output {
width: 100%;
height: 200px;
margin: 0 auto;
margin-top: 10px;
border-left: 0px;
border-right: 0px;
padding-left: 0px;
padding-right: 0px;
display: none;
background-color: black;
color: white;
font-family: 'Lucida Console', Monaco, monospace;
outline: none;
}
.source_code {
}
</style>
<link rel="stylesheet" type="text/css" href="highlight.css">
</head>
<body>
<div class="emscripten_border">
<canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" tabindex=-1></canvas>
</div>
<textarea id="output" rows="8"></textarea>
<div class="source_code">@htmlified_source_code@</div>
<script type='text/javascript'>
var Module = {
preRun: [],
postRun: [],
print: (function() {
var element = document.getElementById('output');
if (element) element.value = ''; // clear browser cache
return function(text) {
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
// These replacements are necessary if you render to raw HTML
//text = text.replace(/&/g, "&amp;");
//text = text.replace(/</g, "&lt;");
//text = text.replace(/>/g, "&gt;");
//text = text.replace('\n', '<br>', 'g');
console.log(text);
if (element) {
element.value += text + "\n";
element.scrollTop = element.scrollHeight; // focus on bottom
}
};
})(),
canvas: (() => {
var canvas = document.getElementById('canvas');
// As a default initial behavior, pop up an alert when webgl context is lost. To make your
// application robust, you may want to override this behavior before shipping!
// See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
canvas.addEventListener("webglcontextlost", (e) => { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
return canvas;
})(),
setStatus: (text) => {},
totalDependencies: 0,
monitorRunDependencies: (left) => {
this.totalDependencies = Math.max(this.totalDependencies, left);
Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
}
};
Module.setStatus('Downloading...');
window.onerror = (event) => {
// TODO: do not warn on ok events like simulating an infinite loop or exitStatus
Module.setStatus('Exception thrown, see JavaScript console');
Module.setStatus = (text) => {
if (text) console.error('[post-exception status] ' + text);
};
};
</script>
<script async type="text/javascript" src="index.js"></script>
</body>
</html>