Squashed 'external/stb/stb/' content from commit c39c7023e

git-subtree-dir: external/stb/stb
git-subtree-split: c39c7023ebb833ce099750fe35509aca5662695e
This commit is contained in:
2023-08-01 20:11:22 +02:00
commit 3cd551098b
422 changed files with 96140 additions and 0 deletions

View File

@ -0,0 +1,94 @@
# Font character oversampling for rendering from atlas textures
TL,DR: Run oversample.exe on a windows machine to see the
benefits of oversampling. It will try to use arial.ttf from the
Windows font directory unless you type the name of a .ttf file as
a command-line argument.
## Benefits of oversampling
Oversampling is a mechanism for improving subpixel rendering of characters.
Improving subpixel has a few benefits:
* With horizontal-oversampling, text can remain sharper while still being sub-pixel positioned for better kerning
* Horizontally-oversampled text significantly reduces aliasing when text animates horizontally
* Vertically-oversampled text significantly reduces aliasing when text animates vertically
* Text oversampled in both directions significantly reduces aliasing when text rotates
## What text oversampling is
A common strategy for rendering text is to cache character bitmaps
and reuse them. For hinted characters, every instance of a given
character is always identical, so this works fine. However, stb_truetype
doesn't do hinting.
For anti-aliased characters, you can actually position the characters
with subpixel precision, and get different bitmaps based on that positioning
if you re-render the vector data.
However, if you simply cache a single version of the bitmap and
draw it at different subpixel positions with a GPU, you will get
either the exact same result (if you use point-sampling on the
texture) or linear filtering. Linear filtering will cause a sub-pixel
positioned bitmap to blur further, causing a visible de-sharpening
of the character. (And, since the character wasn't hinted, it was
already blurrier than a hinted one would be, and now it gets even
more blurry.)
You can avoid this by caching multiple variants of a character which
were rendered independently from the vector data. For example, you
might cache 3 versions of a char, at 0, 1/3, and 2/3rds of a pixel
horizontal offset, and always require characters to fall on integer
positions vertically.
When creating a texture atlas for use on GPUs, which support bilinear
filtering, there is a better approach than caching several independent
positions, which is to allow lerping between the versions to allow
finer subpixel positioning. You can achieve these by interleaving
each of the cached bitmaps, but this turns out to be mathematically
equivalent to a simpler operation: oversampling and prefiltering the
characters.
So, setting oversampling of 2x2 in stb_truetype is equivalent to caching
each character in 4 different variations, 1 for each subpixel position
in a 2x2 set.
An advantage of this formulation is that no changes are required to
the rendering code; the exact same quad-rendering code works, it just
uses different texture coordinates. (Note this does potentially increase
texture bandwidth for text rendering since we end up minifying the texture
without using mipmapping, but you probably are not going to be fill-bound
by your text rendering.)
## What about gamma?
Gamma-correction for fonts just doesn't work. This doesn't seem to make
much sense -- it's physically correct, it simulates what we'd see if you
shrunk a font down really far, right?
But you can play with it in the oversample.exe app. If you turn it on,
white-on-black fonts become too thick (i.e. they become too bright), and
black-on-white fonts become too thin (i.e. they are insufficiently dark). There is
no way to adjust the font's inherent thickness (i.e. by switching to
bold) to fix this for both; making the font thicker will make white
text worse, and making the font thinner will make black text worse.
Obviously you could use different fonts for light and dark cases, but
this doesn't seem like a very good way for fonts to work.
Multiple people who have experimented with this independently (me,
Fabian Giesen,and Maxim Shemanarev of Anti-Grain Geometry) have all
concluded that correct gamma-correction does not produce the best
results for fonts. Font rendering just generally looks better without
gamma correction (or possibly with some arbitrary power stuck in
there, but it's not really correcting for gamma at that point). Maybe
this is in part a product of how we're used to fonts being on screens
which has changed how we expect them to look (e.g. perhaps hinting
oversharpens them and prevents the real-world thinning you'd see in
a black-on-white text).
(AGG link on text rendering, including mention of gamma:
http://www.antigrain.com/research/font_rasterization/ )
Nevertheless, even if you turn on gamma-correction, you will find that
oversampling still helps in many cases for small fonts.

332
tests/oversample/main.c Normal file
View File

@ -0,0 +1,332 @@
#pragma warning(disable:4244; disable:4305; disable:4018)
#include <assert.h>
#include <ctype.h>
#define STB_WINMAIN
#include "stb_wingraph.h"
#define STB_TRUETYPE_IMPLEMENTATION
#define STB_RECT_PACK_IMPLEMENTATION
#include "stb_rect_pack.h"
#include "stb_truetype.h"
#ifndef WINGDIAPI
#define CALLBACK __stdcall
#define WINGDIAPI __declspec(dllimport)
#define APIENTRY __stdcall
#endif
#include <gl/gl.h>
#include <gl/glu.h>
#define GL_FRAMEBUFFER_SRGB_EXT 0x8DB9
#define SIZE_X 1024
#define SIZE_Y 768
stbtt_packedchar chardata[6][128];
int sx=SIZE_X, sy=SIZE_Y;
#define BITMAP_W 512
#define BITMAP_H 512
unsigned char temp_bitmap[BITMAP_W][BITMAP_H];
unsigned char ttf_buffer[1 << 25];
GLuint font_tex;
float scale[2] = { 24.0f, 14.0f };
int sf[6] = { 0,1,2, 0,1,2 };
void load_fonts(void)
{
stbtt_pack_context pc;
int i;
FILE *f;
char filename[256];
char *win = getenv("windir");
if (win == NULL) win = getenv("SystemRoot");
f = fopen(stb_wingraph_commandline, "rb");
if (!f) {
if (win == NULL)
sprintf(filename, "arial.ttf", win);
else
sprintf(filename, "%s/fonts/arial.ttf", win);
f = fopen(filename, "rb");
if (!f) exit(0);
}
fread(ttf_buffer, 1, 1<<25, f);
stbtt_PackBegin(&pc, temp_bitmap[0], BITMAP_W, BITMAP_H, 0, 1, NULL);
for (i=0; i < 2; ++i) {
stbtt_PackSetOversampling(&pc, 1, 1);
stbtt_PackFontRange(&pc, ttf_buffer, 0, scale[i], 32, 95, chardata[i*3+0]+32);
stbtt_PackSetOversampling(&pc, 2, 2);
stbtt_PackFontRange(&pc, ttf_buffer, 0, scale[i], 32, 95, chardata[i*3+1]+32);
stbtt_PackSetOversampling(&pc, 3, 1);
stbtt_PackFontRange(&pc, ttf_buffer, 0, scale[i], 32, 95, chardata[i*3+2]+32);
}
stbtt_PackEnd(&pc);
glGenTextures(1, &font_tex);
glBindTexture(GL_TEXTURE_2D, font_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, BITMAP_W, BITMAP_H, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}
int black_on_white;
void draw_init(void)
{
glDisable(GL_CULL_FACE);
glDisable(GL_TEXTURE_2D);
glDisable(GL_LIGHTING);
glDisable(GL_DEPTH_TEST);
glViewport(0,0,sx,sy);
if (black_on_white)
glClearColor(255,255,255,0);
else
glClearColor(0,0,0,0);
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0,sx,sy,0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void drawBoxTC(float x0, float y0, float x1, float y1, float s0, float t0, float s1, float t1)
{
glTexCoord2f(s0,t0); glVertex2f(x0,y0);
glTexCoord2f(s1,t0); glVertex2f(x1,y0);
glTexCoord2f(s1,t1); glVertex2f(x1,y1);
glTexCoord2f(s0,t1); glVertex2f(x0,y1);
}
int integer_align;
void print(float x, float y, int font, char *text)
{
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, font_tex);
glBegin(GL_QUADS);
while (*text) {
stbtt_aligned_quad q;
stbtt_GetPackedQuad(chardata[font], BITMAP_W, BITMAP_H, *text++, &x, &y, &q, font ? 0 : integer_align);
drawBoxTC(q.x0,q.y0,q.x1,q.y1, q.s0,q.t0,q.s1,q.t1);
}
glEnd();
}
int font=3;
int translating;
int rotating=0;
int srgb=0;
float rotate_t, translate_t;
int show_tex;
void draw_world(void)
{
int sfont = sf[font];
float x = 20;
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
if (black_on_white)
glColor3f(0,0,0);
else
glColor3f(1,1,1);
print(80, 30, sfont, "Controls:");
print(100, 60, sfont, "S: toggle font size");
print(100, 85, sfont, "O: toggle oversampling");
print(100,110, sfont, "T: toggle translation");
print(100,135, sfont, "R: toggle rotation");
print(100,160, sfont, "P: toggle pixel-snap (only non-oversampled)");
print(100,185, sfont, "G: toggle srgb gamma-correction");
if (black_on_white)
print(100,210, sfont, "B: toggle to white-on-black");
else
print(100,210, sfont, "B: toggle to black-on-white");
print(100,235, sfont, "V: view font texture");
print(80, 300, sfont, "Current font:");
if (!show_tex) {
if (font < 3)
print(100, 350, sfont, "Font height: 24 pixels");
else
print(100, 350, sfont, "Font height: 14 pixels");
}
if (font%3==1)
print(100, 325, sfont, "2x2 oversampled text at 1:1");
else if (font%3 == 2)
print(100, 325, sfont, "3x1 oversampled text at 1:1");
else if (integer_align)
print(100, 325, sfont, "1:1 text, one texel = one pixel, snapped to integer coordinates");
else
print(100, 325, sfont, "1:1 text, one texel = one pixel");
if (show_tex) {
glBegin(GL_QUADS);
drawBoxTC(200,400, 200+BITMAP_W,300+BITMAP_H, 0,0,1,1);
glEnd();
} else {
glMatrixMode(GL_MODELVIEW);
glTranslatef(200,350,0);
if (translating)
x += fmod(translate_t*8,30);
if (rotating) {
glTranslatef(100,150,0);
glRotatef(rotate_t*2,0,0,1);
glTranslatef(-100,-150,0);
}
print(x,100, font, "This is a test");
print(x,130, font, "Now is the time for all good men to come to the aid of their country.");
print(x,160, font, "The quick brown fox jumps over the lazy dog.");
print(x,190, font, "0123456789");
}
}
void draw(void)
{
draw_init();
draw_world();
stbwingraph_SwapBuffers(NULL);
}
static int initialized=0;
static float last_dt;
int move[4];
int raw_mouse_x, raw_mouse_y;
int loopmode(float dt, int real, int in_client)
{
float actual_dt = dt;
if (!initialized) return 0;
rotate_t += dt;
translate_t += dt;
// music_sim();
if (!real)
return 0;
if (dt > 0.25) dt = 0.25;
if (dt < 0.01) dt = 0.01;
draw();
return 0;
}
int winproc(void *data, stbwingraph_event *e)
{
switch (e->type) {
case STBWGE_create:
break;
case STBWGE_char:
switch(e->key) {
case 27:
stbwingraph_ShowCursor(NULL,1);
return STBWINGRAPH_winproc_exit;
break;
case 'o': case 'O':
font = (font+1) % 3 + (font/3)*3;
break;
case 's': case 'S':
font = (font+3) % 6;
break;
case 't': case 'T':
translating = !translating;
translate_t = 0;
break;
case 'r': case 'R':
rotating = !rotating;
rotate_t = 0;
break;
case 'p': case 'P':
integer_align = !integer_align;
break;
case 'g': case 'G':
srgb = !srgb;
if (srgb)
glEnable(GL_FRAMEBUFFER_SRGB_EXT);
else
glDisable(GL_FRAMEBUFFER_SRGB_EXT);
break;
case 'v': case 'V':
show_tex = !show_tex;
break;
case 'b': case 'B':
black_on_white = !black_on_white;
break;
}
break;
case STBWGE_mousemove:
raw_mouse_x = e->mx;
raw_mouse_y = e->my;
break;
#if 0
case STBWGE_mousewheel: do_mouse(e,0,0); break;
case STBWGE_leftdown: do_mouse(e, 1,0); break;
case STBWGE_leftup: do_mouse(e,-1,0); break;
case STBWGE_rightdown: do_mouse(e,0, 1); break;
case STBWGE_rightup: do_mouse(e,0,-1); break;
#endif
case STBWGE_keydown:
if (e->key == VK_RIGHT) move[0] = 1;
if (e->key == VK_LEFT) move[1] = 1;
if (e->key == VK_UP) move[2] = 1;
if (e->key == VK_DOWN) move[3] = 1;
break;
case STBWGE_keyup:
if (e->key == VK_RIGHT) move[0] = 0;
if (e->key == VK_LEFT) move[1] = 0;
if (e->key == VK_UP) move[2] = 0;
if (e->key == VK_DOWN) move[3] = 0;
break;
case STBWGE_size:
sx = e->width;
sy = e->height;
loopmode(0,1,0);
break;
case STBWGE_draw:
if (initialized)
loopmode(0,1,0);
break;
default:
return STBWINGRAPH_unprocessed;
}
return 0;
}
void stbwingraph_main(void)
{
stbwingraph_Priority(2);
stbwingraph_CreateWindow(1, winproc, NULL, "tt", SIZE_X,SIZE_Y, 0, 1, 0, 0);
stbwingraph_ShowCursor(NULL, 0);
load_fonts();
initialized = 1;
stbwingraph_MainLoop(loopmode, 0.016f); // 30 fps = 0.33
}

View File

@ -0,0 +1,97 @@
# Microsoft Developer Studio Project File - Name="oversample" - Package Owner=<4>
# Microsoft Developer Studio Generated Build File, Format Version 6.00
# ** DO NOT EDIT **
# TARGTYPE "Win32 (x86) Application" 0x0101
CFG=oversample - Win32 Debug
!MESSAGE This is not a valid makefile. To build this project using NMAKE,
!MESSAGE use the Export Makefile command and run
!MESSAGE
!MESSAGE NMAKE /f "oversample.mak".
!MESSAGE
!MESSAGE You can specify a configuration when running NMAKE
!MESSAGE by defining the macro CFG on the command line. For example:
!MESSAGE
!MESSAGE NMAKE /f "oversample.mak" CFG="oversample - Win32 Debug"
!MESSAGE
!MESSAGE Possible choices for configuration are:
!MESSAGE
!MESSAGE "oversample - Win32 Release" (based on "Win32 (x86) Application")
!MESSAGE "oversample - Win32 Debug" (based on "Win32 (x86) Application")
!MESSAGE
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
CPP=cl.exe
MTL=midl.exe
RSC=rc.exe
!IF "$(CFG)" == "oversample - Win32 Release"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 0
# PROP BASE Output_Dir "Release"
# PROP BASE Intermediate_Dir "Release"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "Release"
# PROP Intermediate_Dir "Release"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c
# ADD CPP /nologo /W3 /WX /GX /O2 /I "c:\sean\prj\stb" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /FD /c
# SUBTRACT CPP /YX
# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32
# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32
# ADD BASE RSC /l 0x409 /d "NDEBUG"
# ADD RSC /l 0x409 /d "NDEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386
# SUBTRACT LINK32 /map /debug
!ELSEIF "$(CFG)" == "oversample - Win32 Debug"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 1
# PROP BASE Output_Dir "Debug"
# PROP BASE Intermediate_Dir "Debug"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 1
# PROP Output_Dir "Debug"
# PROP Intermediate_Dir "Debug"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /GZ /c
# ADD CPP /nologo /W3 /WX /Gm /GX /Zi /Od /I "c:\sean\prj\stb" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /FD /GZ /c
# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32
# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32
# ADD BASE RSC /l 0x409 /d "_DEBUG"
# ADD RSC /l 0x409 /d "_DEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept
# ADD LINK32 kernel32.lib user32.lib gdi32.lib advapi32.lib winspool.lib comdlg32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /incremental:no /debug /machine:I386 /pdbtype:sept
!ENDIF
# Begin Target
# Name "oversample - Win32 Release"
# Name "oversample - Win32 Debug"
# Begin Source File
SOURCE=.\main.c
# End Source File
# End Target
# End Project

View File

@ -0,0 +1,29 @@
Microsoft Developer Studio Workspace File, Format Version 6.00
# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
###############################################################################
Project: "oversample"=.\oversample.dsp - Package Owner=<4>
Package=<5>
{{{
}}}
Package=<4>
{{{
}}}
###############################################################################
Global:
Package=<5>
{{{
}}}
Package=<3>
{{{
}}}
###############################################################################

Binary file not shown.

View File

@ -0,0 +1,829 @@
// stb_wingraph.h v0.01 - public domain windows graphics programming
// wraps WinMain, ChoosePixelFormat, ChangeDisplayResolution, etc. for
// doing OpenGL graphics
//
// in ONE source file, put '#define STB_DEFINE' before including this
// OR put '#define STB_WINMAIN' to define a WinMain that calls stbwingraph_main(void)
//
// @TODO:
// 2d rendering interface (that can be done easily in software)
// STB_WINGRAPH_SOFTWARE -- 2d software rendering only
// STB_WINGRAPH_OPENGL -- OpenGL only
#ifndef INCLUDE_STB_WINGRAPH_H
#define INCLUDE_STB_WINGRAPH_H
#ifdef STB_WINMAIN
#ifndef STB_DEFINE
#define STB_DEFINE
#define STB_WINGRAPH_DISABLE_DEFINE_AT_END
#endif
#endif
#ifdef STB_DEFINE
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "user32.lib")
#endif
#ifdef __cplusplus
#define STB_EXTERN extern "C"
#else
#define STB_EXTERN
#endif
#ifdef STB_DEFINE
#ifndef _WINDOWS_
#ifdef APIENTRY
#undef APIENTRY
#endif
#ifdef WINGDIAPI
#undef WINGDIAPI
#endif
#define _WIN32_WINNT 0x0400 // WM_MOUSEWHEEL
#include <windows.h>
#endif
#include <stdio.h>
#include <math.h>
#include <time.h>
#include <string.h>
#include <assert.h>
#endif
typedef void * stbwingraph_hwnd;
typedef void * stbwingraph_hinstance;
enum
{
STBWINGRAPH_unprocessed = -(1 << 24),
STBWINGRAPH_do_not_show,
STBWINGRAPH_winproc_exit,
STBWINGRAPH_winproc_update,
STBWINGRAPH_update_exit,
STBWINGRAPH_update_pause,
};
typedef enum
{
STBWGE__none=0,
STBWGE_create,
STBWGE_create_postshow,
STBWGE_draw,
STBWGE_destroy,
STBWGE_char,
STBWGE_keydown,
STBWGE_syskeydown,
STBWGE_keyup,
STBWGE_syskeyup,
STBWGE_deactivate,
STBWGE_activate,
STBWGE_size,
STBWGE_mousemove ,
STBWGE_leftdown , STBWGE_leftup ,
STBWGE_middledown, STBWGE_middleup,
STBWGE_rightdown , STBWGE_rightup ,
STBWGE_mousewheel,
} stbwingraph_event_type;
typedef struct
{
stbwingraph_event_type type;
// for input events (mouse, keyboard)
int mx,my; // mouse x & y
int dx,dy;
int shift, ctrl, alt;
// for keyboard events
int key;
// for STBWGE_size:
int width, height;
// for STBWGE_crate
int did_share_lists; // if true, wglShareLists succeeded
void *handle;
} stbwingraph_event;
typedef int (*stbwingraph_window_proc)(void *data, stbwingraph_event *event);
extern stbwingraph_hinstance stbwingraph_app;
extern stbwingraph_hwnd stbwingraph_primary_window;
extern int stbwingraph_request_fullscreen;
extern int stbwingraph_request_windowed;
STB_EXTERN void stbwingraph_ods(char *str, ...);
STB_EXTERN int stbwingraph_MessageBox(stbwingraph_hwnd win, unsigned int type,
char *caption, char *text, ...);
STB_EXTERN int stbwingraph_ChangeResolution(unsigned int w, unsigned int h,
unsigned int bits, int use_message_box);
STB_EXTERN int stbwingraph_SetPixelFormat(stbwingraph_hwnd win, int color_bits,
int alpha_bits, int depth_bits, int stencil_bits, int accum_bits);
STB_EXTERN int stbwingraph_DefineClass(void *hinstance, char *iconname);
STB_EXTERN void stbwingraph_SwapBuffers(void *win);
STB_EXTERN void stbwingraph_Priority(int n);
STB_EXTERN void stbwingraph_MakeFonts(void *window, int font_base);
STB_EXTERN void stbwingraph_ShowWindow(void *window);
STB_EXTERN void *stbwingraph_CreateWindow(int primary, stbwingraph_window_proc func, void *data, char *text, int width, int height, int fullscreen, int resizeable, int dest_alpha, int stencil);
STB_EXTERN void *stbwingraph_CreateWindowSimple(stbwingraph_window_proc func, int width, int height);
STB_EXTERN void *stbwingraph_CreateWindowSimpleFull(stbwingraph_window_proc func, int fullscreen, int ww, int wh, int fw, int fh);
STB_EXTERN void stbwingraph_DestroyWindow(void *window);
STB_EXTERN void stbwingraph_ShowCursor(void *window, int visible);
STB_EXTERN float stbwingraph_GetTimestep(float minimum_time);
STB_EXTERN void stbwingraph_SetGLWindow(void *win);
typedef int (*stbwingraph_update)(float timestep, int real, int in_client);
STB_EXTERN int stbwingraph_MainLoop(stbwingraph_update func, float mintime);
#ifdef STB_DEFINE
stbwingraph_hinstance stbwingraph_app;
stbwingraph_hwnd stbwingraph_primary_window;
int stbwingraph_request_fullscreen;
int stbwingraph_request_windowed;
void stbwingraph_ods(char *str, ...)
{
char buffer[1024];
va_list v;
va_start(v,str);
vsprintf(buffer, str, v);
va_end(v);
OutputDebugString(buffer);
}
int stbwingraph_MessageBox(stbwingraph_hwnd win, unsigned int type, char *caption, char *text, ...)
{
va_list v;
char buffer[1024];
va_start(v, text);
vsprintf(buffer, text, v);
va_end(v);
return MessageBox(win, buffer, caption, type);
}
void stbwingraph_Priority(int n)
{
int p;
switch (n) {
case -1: p = THREAD_PRIORITY_BELOW_NORMAL; break;
case 0: p = THREAD_PRIORITY_NORMAL; break;
case 1: p = THREAD_PRIORITY_ABOVE_NORMAL; break;
default:
if (n < 0) p = THREAD_PRIORITY_LOWEST;
else p = THREAD_PRIORITY_HIGHEST;
}
SetThreadPriority(GetCurrentThread(), p);
}
static void stbwingraph_ResetResolution(void)
{
ChangeDisplaySettings(NULL, 0);
}
static void stbwingraph_RegisterResetResolution(void)
{
static int done=0;
if (!done) {
done = 1;
atexit(stbwingraph_ResetResolution);
}
}
int stbwingraph_ChangeResolution(unsigned int w, unsigned int h, unsigned int bits, int use_message_box)
{
DEVMODE mode;
int res;
int i, tries=0;
for (i=0; ; ++i) {
int success = EnumDisplaySettings(NULL, i, &mode);
if (!success) break;
if (mode.dmBitsPerPel == bits && mode.dmPelsWidth == w && mode.dmPelsHeight == h) {
++tries;
success = ChangeDisplaySettings(&mode, CDS_FULLSCREEN);
if (success == DISP_CHANGE_SUCCESSFUL) {
stbwingraph_RegisterResetResolution();
return TRUE;
}
break;
}
}
if (!tries) {
if (use_message_box)
stbwingraph_MessageBox(stbwingraph_primary_window, MB_ICONERROR, NULL, "The resolution %d x %d x %d-bits is not supported.", w, h, bits);
return FALSE;
}
// we tried but failed, so try explicitly doing it without specifying refresh rate
// Win95 support logic
mode.dmBitsPerPel = bits;
mode.dmPelsWidth = w;
mode.dmPelsHeight = h;
mode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
res = ChangeDisplaySettings(&mode, CDS_FULLSCREEN);
switch (res) {
case DISP_CHANGE_SUCCESSFUL:
stbwingraph_RegisterResetResolution();
return TRUE;
case DISP_CHANGE_RESTART:
if (use_message_box)
stbwingraph_MessageBox(stbwingraph_primary_window, MB_ICONERROR, NULL, "Please set your desktop to %d-bit color and then try again.");
return FALSE;
case DISP_CHANGE_FAILED:
if (use_message_box)
stbwingraph_MessageBox(stbwingraph_primary_window, MB_ICONERROR, NULL, "The hardware failed to change modes.");
return FALSE;
case DISP_CHANGE_BADMODE:
if (use_message_box)
stbwingraph_MessageBox(stbwingraph_primary_window, MB_ICONERROR, NULL, "The resolution %d x %d x %d-bits is not supported.", w, h, bits);
return FALSE;
default:
if (use_message_box)
stbwingraph_MessageBox(stbwingraph_primary_window, MB_ICONERROR, NULL, "An unknown error prevented a change to a %d x %d x %d-bit display.", w, h, bits);
return FALSE;
}
}
int stbwingraph_SetPixelFormat(stbwingraph_hwnd win, int color_bits, int alpha_bits, int depth_bits, int stencil_bits, int accum_bits)
{
HDC dc = GetDC(win);
PIXELFORMATDESCRIPTOR pfd = { sizeof(pfd) };
int pixel_format;
pfd.nVersion = 1;
pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER;
pfd.dwLayerMask = PFD_MAIN_PLANE;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = color_bits;
pfd.cAlphaBits = alpha_bits;
pfd.cDepthBits = depth_bits;
pfd.cStencilBits = stencil_bits;
pfd.cAccumBits = accum_bits;
pixel_format = ChoosePixelFormat(dc, &pfd);
if (!pixel_format) return FALSE;
if (!DescribePixelFormat(dc, pixel_format, sizeof(PIXELFORMATDESCRIPTOR), &pfd))
return FALSE;
SetPixelFormat(dc, pixel_format, &pfd);
return TRUE;
}
typedef struct
{
// app data
stbwingraph_window_proc func;
void *data;
// creation parameters
int color, alpha, depth, stencil, accum;
HWND share_window;
HWND window;
// internal data
HGLRC rc;
HDC dc;
int hide_mouse;
int in_client;
int active;
int did_share_lists;
int mx,my; // last mouse positions
} stbwingraph__window;
static void stbwingraph__inclient(stbwingraph__window *win, int state)
{
if (state != win->in_client) {
win->in_client = state;
if (win->hide_mouse)
ShowCursor(!state);
}
}
static void stbwingraph__key(stbwingraph_event *e, int type, int key, stbwingraph__window *z)
{
e->type = type;
e->key = key;
e->shift = (GetKeyState(VK_SHIFT) < 0);
e->ctrl = (GetKeyState(VK_CONTROL) < 0);
e->alt = (GetKeyState(VK_MENU) < 0);
if (z) {
e->mx = z->mx;
e->my = z->my;
} else {
e->mx = e->my = 0;
}
e->dx = e->dy = 0;
}
static void stbwingraph__mouse(stbwingraph_event *e, int type, WPARAM wparam, LPARAM lparam, int capture, void *wnd, stbwingraph__window *z)
{
static int captured = 0;
e->type = type;
e->mx = (short) LOWORD(lparam);
e->my = (short) HIWORD(lparam);
if (!z || z->mx == -(1 << 30)) {
e->dx = e->dy = 0;
} else {
e->dx = e->mx - z->mx;
e->dy = e->my - z->my;
}
e->shift = (wparam & MK_SHIFT) != 0;
e->ctrl = (wparam & MK_CONTROL) != 0;
e->alt = (wparam & MK_ALT) != 0;
if (z) {
z->mx = e->mx;
z->my = e->my;
}
if (capture) {
if (!captured && capture == 1)
SetCapture(wnd);
captured += capture;
if (!captured && capture == -1)
ReleaseCapture();
if (captured < 0) captured = 0;
}
}
static void stbwingraph__mousewheel(stbwingraph_event *e, int type, WPARAM wparam, LPARAM lparam, int capture, void *wnd, stbwingraph__window *z)
{
// lparam seems bogus!
static int captured = 0;
e->type = type;
if (z) {
e->mx = z->mx;
e->my = z->my;
}
e->dx = e->dy = 0;
e->shift = (wparam & MK_SHIFT) != 0;
e->ctrl = (wparam & MK_CONTROL) != 0;
e->alt = (GetKeyState(VK_MENU) < 0);
e->key = ((int) wparam >> 16);
}
int stbwingraph_force_update;
static int WINAPI stbwingraph_WinProc(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
int allow_default = TRUE;
stbwingraph_event e = { STBWGE__none };
// the following line is wrong for 64-bit windows, but VC6 doesn't have GetWindowLongPtr
stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(wnd, GWL_USERDATA);
switch (msg) {
case WM_CREATE:
{
LPCREATESTRUCT lpcs = (LPCREATESTRUCT) lparam;
assert(z == NULL);
z = (stbwingraph__window *) lpcs->lpCreateParams;
SetWindowLong(wnd, GWL_USERDATA, (LONG) z);
z->dc = GetDC(wnd);
if (stbwingraph_SetPixelFormat(wnd, z->color, z->alpha, z->depth, z->stencil, z->accum)) {
z->rc = wglCreateContext(z->dc);
if (z->rc) {
e.type = STBWGE_create;
z->did_share_lists = FALSE;
if (z->share_window) {
stbwingraph__window *y = (stbwingraph__window *) GetWindowLong(z->share_window, GWL_USERDATA);
if (wglShareLists(z->rc, y->rc))
z->did_share_lists = TRUE;
}
wglMakeCurrent(z->dc, z->rc);
return 0;
}
}
return -1;
}
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(wnd, &ps);
SelectObject(hdc, GetStockObject(NULL_BRUSH));
e.type = STBWGE_draw;
e.handle = wnd;
z->func(z->data, &e);
EndPaint(wnd, &ps);
return 0;
}
case WM_DESTROY:
e.type = STBWGE_destroy;
e.handle = wnd;
if (z && z->func)
z->func(z->data, &e);
wglMakeCurrent(NULL, NULL) ;
if (z) {
if (z->rc) wglDeleteContext(z->rc);
z->dc = 0;
z->rc = 0;
}
if (wnd == stbwingraph_primary_window)
PostQuitMessage (0);
return 0;
case WM_CHAR: stbwingraph__key(&e, STBWGE_char , wparam, z); break;
case WM_KEYDOWN: stbwingraph__key(&e, STBWGE_keydown, wparam, z); break;
case WM_KEYUP: stbwingraph__key(&e, STBWGE_keyup , wparam, z); break;
case WM_NCMOUSEMOVE: stbwingraph__inclient(z,0); break;
case WM_MOUSEMOVE: stbwingraph__inclient(z,1); stbwingraph__mouse(&e, STBWGE_mousemove, wparam, lparam,0,wnd, z); break;
case WM_LBUTTONDOWN: stbwingraph__mouse(&e, STBWGE_leftdown, wparam, lparam,1,wnd, z); break;
case WM_MBUTTONDOWN: stbwingraph__mouse(&e, STBWGE_middledown, wparam, lparam,1,wnd, z); break;
case WM_RBUTTONDOWN: stbwingraph__mouse(&e, STBWGE_rightdown, wparam, lparam,1,wnd, z); break;
case WM_LBUTTONUP: stbwingraph__mouse(&e, STBWGE_leftup, wparam, lparam,-1,wnd, z); break;
case WM_MBUTTONUP: stbwingraph__mouse(&e, STBWGE_middleup, wparam, lparam,-1,wnd, z); break;
case WM_RBUTTONUP: stbwingraph__mouse(&e, STBWGE_rightup, wparam, lparam,-1,wnd, z); break;
case WM_MOUSEWHEEL: stbwingraph__mousewheel(&e, STBWGE_mousewheel, wparam, lparam,0,wnd, z); break;
case WM_ACTIVATE:
allow_default = FALSE;
if (LOWORD(wparam)==WA_INACTIVE ) {
wglMakeCurrent(z->dc, NULL);
e.type = STBWGE_deactivate;
z->active = FALSE;
} else {
wglMakeCurrent(z->dc, z->rc);
e.type = STBWGE_activate;
z->active = TRUE;
}
e.handle = wnd;
z->func(z->data, &e);
return 0;
case WM_SIZE: {
RECT rect;
allow_default = FALSE;
GetClientRect(wnd, &rect);
e.type = STBWGE_size;
e.width = rect.right;
e.height = rect.bottom;
e.handle = wnd;
z->func(z->data, &e);
return 0;
}
default:
return DefWindowProc (wnd, msg, wparam, lparam);
}
if (e.type != STBWGE__none) {
int n;
e.handle = wnd;
n = z->func(z->data, &e);
if (n == STBWINGRAPH_winproc_exit) {
PostQuitMessage(0);
n = 0;
}
if (n == STBWINGRAPH_winproc_update) {
stbwingraph_force_update = TRUE;
return 1;
}
if (n != STBWINGRAPH_unprocessed)
return n;
}
return DefWindowProc (wnd, msg, wparam, lparam);
}
int stbwingraph_DefineClass(HINSTANCE hInstance, char *iconname)
{
WNDCLASSEX wndclass;
stbwingraph_app = hInstance;
wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_OWNDC;
wndclass.lpfnWndProc = (WNDPROC) stbwingraph_WinProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(hInstance, iconname);
wndclass.hCursor = LoadCursor(NULL,IDC_ARROW);
wndclass.hbrBackground = GetStockObject(NULL_BRUSH);
wndclass.lpszMenuName = "zwingraph";
wndclass.lpszClassName = "zwingraph";
wndclass.hIconSm = NULL;
if (!RegisterClassEx(&wndclass))
return FALSE;
return TRUE;
}
void stbwingraph_ShowWindow(void *window)
{
stbwingraph_event e = { STBWGE_create_postshow };
stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(window, GWL_USERDATA);
ShowWindow(window, SW_SHOWNORMAL);
InvalidateRect(window, NULL, TRUE);
UpdateWindow(window);
e.handle = window;
z->func(z->data, &e);
}
void *stbwingraph_CreateWindow(int primary, stbwingraph_window_proc func, void *data, char *text,
int width, int height, int fullscreen, int resizeable, int dest_alpha, int stencil)
{
HWND win;
DWORD dwstyle;
stbwingraph__window *z = (stbwingraph__window *) malloc(sizeof(*z));
if (z == NULL) return NULL;
memset(z, 0, sizeof(*z));
z->color = 24;
z->depth = 24;
z->alpha = dest_alpha;
z->stencil = stencil;
z->func = func;
z->data = data;
z->mx = -(1 << 30);
z->my = 0;
if (primary) {
if (stbwingraph_request_windowed)
fullscreen = FALSE;
else if (stbwingraph_request_fullscreen)
fullscreen = TRUE;
}
if (fullscreen) {
#ifdef STB_SIMPLE
stbwingraph_ChangeResolution(width, height, 32, 1);
#else
if (!stbwingraph_ChangeResolution(width, height, 32, 0))
return NULL;
#endif
dwstyle = WS_POPUP | WS_CLIPSIBLINGS;
} else {
RECT rect;
dwstyle = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
if (resizeable)
dwstyle |= WS_SIZEBOX | WS_MAXIMIZEBOX;
rect.top = 0;
rect.left = 0;
rect.right = width;
rect.bottom = height;
AdjustWindowRect(&rect, dwstyle, FALSE);
width = rect.right - rect.left;
height = rect.bottom - rect.top;
}
win = CreateWindow("zwingraph", text ? text : "sample", dwstyle,
CW_USEDEFAULT,0, width, height,
NULL, NULL, stbwingraph_app, z);
if (win == NULL) return win;
if (primary) {
if (stbwingraph_primary_window)
stbwingraph_DestroyWindow(stbwingraph_primary_window);
stbwingraph_primary_window = win;
}
{
stbwingraph_event e = { STBWGE_create };
stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(win, GWL_USERDATA);
z->window = win;
e.did_share_lists = z->did_share_lists;
e.handle = win;
if (z->func(z->data, &e) != STBWINGRAPH_do_not_show)
stbwingraph_ShowWindow(win);
}
return win;
}
void *stbwingraph_CreateWindowSimple(stbwingraph_window_proc func, int width, int height)
{
int fullscreen = 0;
#ifndef _DEBUG
if (width == 640 && height == 480) fullscreen = 1;
if (width == 800 && height == 600) fullscreen = 1;
if (width == 1024 && height == 768) fullscreen = 1;
if (width == 1280 && height == 1024) fullscreen = 1;
if (width == 1600 && height == 1200) fullscreen = 1;
//@TODO: widescreen widths
#endif
return stbwingraph_CreateWindow(1, func, NULL, NULL, width, height, fullscreen, 1, 0, 0);
}
void *stbwingraph_CreateWindowSimpleFull(stbwingraph_window_proc func, int fullscreen, int ww, int wh, int fw, int fh)
{
if (fullscreen == -1) {
#ifdef _DEBUG
fullscreen = 0;
#else
fullscreen = 1;
#endif
}
if (fullscreen) {
if (fw) ww = fw;
if (fh) wh = fh;
}
return stbwingraph_CreateWindow(1, func, NULL, NULL, ww, wh, fullscreen, 1, 0, 0);
}
void stbwingraph_DestroyWindow(void *window)
{
stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(window, GWL_USERDATA);
DestroyWindow(window);
free(z);
if (stbwingraph_primary_window == window)
stbwingraph_primary_window = NULL;
}
void stbwingraph_ShowCursor(void *window, int visible)
{
int hide;
stbwingraph__window *win;
if (!window)
window = stbwingraph_primary_window;
win = (stbwingraph__window *) GetWindowLong((HWND) window, GWL_USERDATA);
hide = !visible;
if (hide != win->hide_mouse) {
win->hide_mouse = hide;
if (!hide)
ShowCursor(TRUE);
else if (win->in_client)
ShowCursor(FALSE);
}
}
float stbwingraph_GetTimestep(float minimum_time)
{
float elapsedTime;
double thisTime;
static double lastTime = -1;
if (lastTime == -1)
lastTime = timeGetTime() / 1000.0 - minimum_time;
for(;;) {
thisTime = timeGetTime() / 1000.0;
elapsedTime = (float) (thisTime - lastTime);
if (elapsedTime >= minimum_time) {
lastTime = thisTime;
return elapsedTime;
}
#if 1
Sleep(2);
#endif
}
}
void stbwingraph_SetGLWindow(void *win)
{
stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(win, GWL_USERDATA);
if (z)
wglMakeCurrent(z->dc, z->rc);
}
void stbwingraph_MakeFonts(void *window, int font_base)
{
wglUseFontBitmaps(GetDC(window ? window : stbwingraph_primary_window), 0, 256, font_base);
}
// returns 1 if WM_QUIT, 0 if 'func' returned 0
int stbwingraph_MainLoop(stbwingraph_update func, float mintime)
{
int needs_drawing = FALSE;
MSG msg;
int is_animating = TRUE;
if (mintime <= 0) mintime = 0.01f;
for(;;) {
int n;
is_animating = TRUE;
// wait for a message if: (a) we're animating and there's already a message
// or (b) we're not animating
if (!is_animating || PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
stbwingraph_force_update = FALSE;
if (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
} else {
return 1; // WM_QUIT
}
// only force a draw for certain messages...
// if I don't do this, we peg at 50% for some reason... must
// be a bug somewhere, because we peg at 100% when rendering...
// very weird... looks like NVIDIA is pumping some messages
// through our pipeline? well, ok, I guess if we can get
// non-user-generated messages we have to do this
if (!stbwingraph_force_update) {
switch (msg.message) {
case WM_MOUSEMOVE:
case WM_NCMOUSEMOVE:
break;
case WM_CHAR:
case WM_KEYDOWN:
case WM_KEYUP:
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
case WM_TIMER:
case WM_SIZE:
case WM_ACTIVATE:
needs_drawing = TRUE;
break;
}
} else
needs_drawing = TRUE;
}
// if another message, process that first
// @TODO: i don't think this is working, because I can't key ahead
// in the SVT demo app
if (PeekMessage(&msg, NULL, 0,0, PM_NOREMOVE))
continue;
// and now call update
if (needs_drawing || is_animating) {
int real=1, in_client=1;
if (stbwingraph_primary_window) {
stbwingraph__window *z = (stbwingraph__window *) GetWindowLong(stbwingraph_primary_window, GWL_USERDATA);
if (z && !z->active) {
real = 0;
}
if (z)
in_client = z->in_client;
}
if (stbwingraph_primary_window)
stbwingraph_SetGLWindow(stbwingraph_primary_window);
n = func(stbwingraph_GetTimestep(mintime), real, in_client);
if (n == STBWINGRAPH_update_exit)
return 0; // update_quit
is_animating = (n != STBWINGRAPH_update_pause);
needs_drawing = FALSE;
}
}
}
void stbwingraph_SwapBuffers(void *win)
{
stbwingraph__window *z;
if (win == NULL) win = stbwingraph_primary_window;
z = (stbwingraph__window *) GetWindowLong(win, GWL_USERDATA);
if (z && z->dc)
SwapBuffers(z->dc);
}
#endif
#ifdef STB_WINMAIN
void stbwingraph_main(void);
char *stb_wingraph_commandline;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
{
char buffer[1024];
// add spaces to either side of the string
buffer[0] = ' ';
strcpy(buffer+1, lpCmdLine);
strcat(buffer, " ");
if (strstr(buffer, " -reset ")) {
ChangeDisplaySettings(NULL, 0);
exit(0);
}
if (strstr(buffer, " -window ") || strstr(buffer, " -windowed "))
stbwingraph_request_windowed = TRUE;
else if (strstr(buffer, " -full ") || strstr(buffer, " -fullscreen "))
stbwingraph_request_fullscreen = TRUE;
}
stb_wingraph_commandline = lpCmdLine;
stbwingraph_DefineClass(hInstance, "appicon");
stbwingraph_main();
return 0;
}
#endif
#undef STB_EXTERN
#ifdef STB_WINGRAPH_DISABLE_DEFINE_AT_END
#undef STB_DEFINE
#endif
#endif // INCLUDE_STB_WINGRAPH_H