1035 lines
38 KiB
C++
1035 lines
38 KiB
C++
/*
|
|
** Win32 Direct Write Example Program
|
|
** v1.0.0 - June 16th 2021
|
|
** by Allen Webster allenwebster@4coder.net
|
|
**
|
|
** public domain example program
|
|
** NO WARRANTY IMPLIED; USE AT YOUR OWN RISK
|
|
**
|
|
** *WARNING* this example has not yet been curated and refined to save
|
|
** your time if you are trying to use it for learning. It lacks detailed
|
|
** commentary and is probably sloppy in places.
|
|
**
|
|
*/
|
|
|
|
|
|
// DirectWrite rasterization example
|
|
|
|
#define UNICODE
|
|
#include <windows.h>
|
|
#include <GL\gl.h>
|
|
#include <dwrite_1.h>
|
|
#include <assert.h>
|
|
#include <malloc.h>
|
|
#include <stdint.h>
|
|
typedef int32_t bool32;
|
|
|
|
#include "example_gl_defines.h"
|
|
|
|
HWND
|
|
window_setup(HINSTANCE hInstance);
|
|
|
|
////////////////////////////////
|
|
|
|
static int32_t window_width = 800;
|
|
static int32_t window_height = 600;
|
|
static wchar_t font_path[] = L"C:\\Windows\\Fonts\\arial.ttf";
|
|
static float point_size = 12.f;
|
|
|
|
// This is not a guide in fighting with Windows to let you manage the DPI. Just leave this at 96
|
|
// until you're ready for a whole separate nightmare.
|
|
static float dpi = 96.f;
|
|
|
|
////////////////////////////////
|
|
|
|
struct AutoReleaserClass{
|
|
IUnknown *ptr_member;
|
|
AutoReleaserClass(IUnknown *ptr){
|
|
ptr_member = ptr;
|
|
}
|
|
~AutoReleaserClass(){
|
|
if (ptr_member != 0){
|
|
ptr_member->Release();
|
|
}
|
|
}
|
|
};
|
|
#define DeferRelease(ptr) AutoReleaserClass ptr##_releaser(ptr)
|
|
#define DWCheck(error,r) if ((error) != S_OK){ error = S_OK; r; }
|
|
#define DWCheckPtr(error,ptr,r) if ((ptr) == 0 || (error) != S_OK){ error = S_OK; r; }
|
|
|
|
// Font Data Structure
|
|
|
|
struct Glyph_Metrics{
|
|
float off_x;
|
|
float off_y;
|
|
float advance;
|
|
float xy_w;
|
|
float xy_h;
|
|
float uv_w;
|
|
float uv_h;
|
|
};
|
|
|
|
struct Baked_Font{
|
|
IDWriteFontFace *face;
|
|
GLuint texture;
|
|
Glyph_Metrics *metrics;
|
|
int32_t glyph_count;
|
|
};
|
|
|
|
////////////////////////////////
|
|
|
|
static char vert_source[] =
|
|
"#version 330\n"
|
|
"uniform mat3 pixel_to_normal;\n"
|
|
"in vec2 position;\n"
|
|
"in vec3 tex_position;\n"
|
|
"smooth out vec3 uv;\n"
|
|
"void main(){\n"
|
|
" gl_Position.xy = (pixel_to_normal*vec3(position, 1.f)).xy;\n"
|
|
" gl_Position.z = 0.f;\n"
|
|
" gl_Position.w = 1.f;\n"
|
|
" uv = tex_position;\n"
|
|
"}\n";
|
|
|
|
static char frag_source[] =
|
|
"#version 330\n"
|
|
"smooth in vec3 uv;\n"
|
|
"uniform sampler2DArray tex;\n"
|
|
"uniform float M_value_table[7];\n"
|
|
"layout(location = 0) out vec4 mask;\n"
|
|
"\n"
|
|
"void main(){\n"
|
|
"vec3 S = texture(tex, uv).rgb;\n"
|
|
"int C0 = int(S.r*6 + 0.1); // + 0.1 just incase we have some small rounding taking us below the integer we should be hitting.\n"
|
|
"int C1 = int(S.g*6 + 0.1);\n"
|
|
"int C2 = int(S.b*6 + 0.1);\n"
|
|
"mask.rgb = vec3(M_value_table[C0],\n"
|
|
"M_value_table[C1],\n"
|
|
"M_value_table[C2]);\n"
|
|
"mask.a = 1;\n"
|
|
"}\n";
|
|
|
|
static GLuint uniform_pixel_to_normal;
|
|
static GLuint uniform_tex;
|
|
static GLuint uniform_M_value_table;
|
|
|
|
static GLuint attrib_position;
|
|
static GLuint attrib_tex_position;
|
|
|
|
////////////////////////////////
|
|
|
|
uint32_t
|
|
next_power_of_two(uint32_t x){
|
|
if (x == 0){
|
|
return(1);
|
|
}
|
|
else{
|
|
x -= 1;
|
|
x |= x >> 1;
|
|
x |= x >> 2;
|
|
x |= x >> 4;
|
|
x |= x >> 8;
|
|
x |= x >> 16;
|
|
x += 1;
|
|
return(x);
|
|
}
|
|
}
|
|
|
|
int32_t
|
|
round_up(float x){
|
|
int32_t r = (int32_t)x;
|
|
if ((float)r < x){
|
|
r += 1;
|
|
}
|
|
return(r);
|
|
}
|
|
|
|
void
|
|
draw_string(Baked_Font font, char *text, int32_t x, int32_t y, float r, float g, float b, float a){
|
|
// Get Index Array
|
|
int32_t length = 0;
|
|
for (; text[length] != 0; length += 1);
|
|
uint16_t *indices = (uint16_t*)malloc(sizeof(uint16_t)*length);
|
|
|
|
for (int32_t i = 0; i < length; i += 1){
|
|
uint32_t codepoint = (uint32_t)text[i];
|
|
font.face->GetGlyphIndices(&codepoint, 1, &indices[i]);
|
|
}
|
|
|
|
// Fill Vertices
|
|
int32_t float_per_vertex = 5;
|
|
int32_t byte_per_vertex = float_per_vertex*sizeof(float);
|
|
int32_t vertex_per_character = 6;
|
|
int32_t total_float_count = length*vertex_per_character*float_per_vertex;
|
|
float *vertices = (float*)malloc(sizeof(float)*total_float_count);
|
|
|
|
{
|
|
float layout_x = (float)x;
|
|
float layout_y = (float)y;
|
|
|
|
float *vertex = vertices;
|
|
for (int32_t i = 0; i < length; i += 1){
|
|
uint16_t index = indices[i];
|
|
assert(index < font.glyph_count);
|
|
|
|
float index_f = (float)(index/4);
|
|
float uv_x = 0.5f*(float)((index&1));
|
|
float uv_y = 0.5f*(float)(((index&2) >> 1));
|
|
Glyph_Metrics metrics = font.metrics[index];
|
|
|
|
for (int32_t j = 0; j < vertex_per_character; j += 1){
|
|
float g_x = layout_x + metrics.off_x;
|
|
float g_y = layout_y + metrics.off_y;
|
|
|
|
switch (j){
|
|
case 0:
|
|
{
|
|
vertex[0] = g_x;
|
|
vertex[1] = g_y;
|
|
vertex[2] = uv_x;
|
|
vertex[3] = uv_y;
|
|
}break;
|
|
case 1:
|
|
case 3:
|
|
{
|
|
vertex[0] = g_x;
|
|
vertex[1] = g_y + metrics.xy_h;
|
|
vertex[2] = uv_x;
|
|
vertex[3] = uv_y + metrics.uv_h;
|
|
}break;
|
|
case 2:
|
|
case 4:
|
|
{
|
|
vertex[0] = g_x + metrics.xy_w;
|
|
vertex[1] = g_y;
|
|
vertex[2] = uv_x + metrics.uv_w;
|
|
vertex[3] = uv_y;
|
|
}break;
|
|
case 5:
|
|
{
|
|
vertex[0] = g_x + metrics.xy_w;
|
|
vertex[1] = g_y + metrics.xy_h;
|
|
vertex[2] = uv_x + metrics.uv_w;
|
|
vertex[3] = uv_y + metrics.uv_h;
|
|
}break;
|
|
}
|
|
vertex[4] = index_f;
|
|
vertex += float_per_vertex;
|
|
}
|
|
|
|
layout_x += metrics.advance;
|
|
}
|
|
}
|
|
|
|
// Compute M Values
|
|
float V = r*0.5f + g + b*0.1875f;
|
|
float M_value_table[7];
|
|
|
|
static float Cmax_table[] = {
|
|
0.f,
|
|
0.380392157f,
|
|
0.600000000f,
|
|
0.749019608f,
|
|
0.854901961f,
|
|
0.937254902f,
|
|
1.f,
|
|
};
|
|
static float Cmin_table[] = {
|
|
0.f,
|
|
0.166666667f,
|
|
0.333333333f,
|
|
0.500000000f,
|
|
0.666666667f,
|
|
0.833333333f,
|
|
1.f,
|
|
};
|
|
|
|
static float A = 0.839215686374509f; // 214/255
|
|
static float B = 1.266666666666667f; // 323/255
|
|
float L = (V - A)/(B - A);
|
|
|
|
M_value_table[0] = 0.f;
|
|
for (int32_t i = 1; i <= 5; i += 1){
|
|
float Cmax = Cmax_table[i];
|
|
float Cmin = Cmin_table[i];
|
|
float M = Cmax + (Cmin - Cmax)*L;
|
|
if (M > Cmax){
|
|
M = Cmax;
|
|
}
|
|
if (M < Cmin){
|
|
M = Cmin;
|
|
}
|
|
M_value_table[i] = M*a;
|
|
}
|
|
M_value_table[6] = a;
|
|
|
|
// Draw
|
|
glBlendColor(r, g, b, a);
|
|
glBufferData(GL_ARRAY_BUFFER, total_float_count*sizeof(float), vertices, GL_DYNAMIC_DRAW);
|
|
glActiveTexture(GL_TEXTURE0);
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, font.texture);
|
|
glUniform1i(uniform_tex, 0);
|
|
glUniform1fv(uniform_M_value_table, 7, M_value_table);
|
|
glVertexAttribPointer(attrib_position, 2, GL_FLOAT, GL_FALSE, byte_per_vertex, 0);
|
|
glVertexAttribPointer(attrib_tex_position, 3, GL_FLOAT, GL_FALSE, byte_per_vertex, (void*)(sizeof(float)*2));
|
|
glDrawArrays(GL_TRIANGLES, 0, vertex_per_character*length);
|
|
|
|
free(vertices);
|
|
free(indices);
|
|
}
|
|
|
|
void
|
|
gl_debug(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam){
|
|
assert(!"Bad OpenGL Call!");
|
|
}
|
|
|
|
int
|
|
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){
|
|
HWND wnd = window_setup(hInstance);
|
|
|
|
// OpenGL Setup
|
|
GLuint framebuffer = 0;
|
|
|
|
{
|
|
// Debug
|
|
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_HIGH, 0, 0, GL_TRUE);
|
|
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_MEDIUM, 0, 0, GL_FALSE);
|
|
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_LOW, 0, 0, GL_FALSE);
|
|
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION, 0, 0, GL_FALSE);
|
|
glDebugMessageCallback(gl_debug, 0);
|
|
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
|
|
|
|
// Settings
|
|
glEnable(GL_FRAMEBUFFER_SRGB);
|
|
glEnable(GL_BLEND);
|
|
glBlendFunc(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR);
|
|
|
|
// sRGB Framebuffer
|
|
GLuint frame_texture = 0;
|
|
glGenTextures(1, &frame_texture);
|
|
glBindTexture(GL_TEXTURE_2D, frame_texture);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8, window_width, window_height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
|
|
|
|
glGenFramebuffers(1, &framebuffer);
|
|
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, frame_texture, 0);
|
|
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
|
assert(status == GL_FRAMEBUFFER_COMPLETE);
|
|
|
|
// Shader
|
|
GLuint shader_vert = glCreateShader(GL_VERTEX_SHADER);
|
|
GLuint shader_frag = glCreateShader(GL_FRAGMENT_SHADER);
|
|
|
|
char *vert_source_localized = vert_source;
|
|
char *frag_source_localized = frag_source;
|
|
|
|
glShaderSource(shader_vert, 1, &vert_source_localized, 0);
|
|
glShaderSource(shader_frag, 1, &frag_source_localized, 0);
|
|
|
|
glCompileShader(shader_vert);
|
|
glCompileShader(shader_frag);
|
|
|
|
GLenum error = glGetError();
|
|
assert(error == GL_NO_ERROR);
|
|
|
|
GLuint program = glCreateProgram();
|
|
glAttachShader(program, shader_vert);
|
|
glAttachShader(program, shader_frag);
|
|
glLinkProgram(program);
|
|
|
|
error = glGetError();
|
|
assert(error == GL_NO_ERROR);
|
|
|
|
glUseProgram(program);
|
|
|
|
// Uniforms and Attributes
|
|
uniform_pixel_to_normal = glGetUniformLocation(program, "pixel_to_normal");
|
|
uniform_tex = glGetUniformLocation(program, "tex");
|
|
uniform_M_value_table = glGetUniformLocation(program, "M_value_table");
|
|
|
|
attrib_position = glGetAttribLocation(program, "position");
|
|
attrib_tex_position = glGetAttribLocation(program, "tex_position");
|
|
|
|
float mat[9];
|
|
mat[0] = 2.f/(float)window_width; mat[3] = 0.f; mat[6] = -1.f;
|
|
mat[1] = 0.f; mat[4] = -2.f/(float)window_height; mat[7] = 1.f,
|
|
mat[2] = 0.f; mat[5] = 0.f; mat[8] = 1.f,
|
|
glUniformMatrix3fv(uniform_pixel_to_normal, 1, GL_FALSE, mat);
|
|
|
|
// Viewport
|
|
glViewport(0, 0, window_width, window_height);
|
|
|
|
// Vertex Array Object
|
|
GLuint VAO = 0;
|
|
glGenVertexArrays(1, &VAO);
|
|
glBindVertexArray(VAO);
|
|
|
|
// Data Buffer
|
|
GLuint buffer = 0;
|
|
glGenBuffers(1, &buffer);
|
|
glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
|
|
|
glEnableVertexAttribArray(attrib_position);
|
|
glEnableVertexAttribArray(attrib_tex_position);
|
|
}
|
|
|
|
// Font Setup
|
|
Baked_Font font = {0};
|
|
|
|
{
|
|
COLORREF back_color = RGB(0,0,0);
|
|
COLORREF fore_color = RGB(255,255,255);
|
|
|
|
HRESULT error = 0;
|
|
|
|
// Factory
|
|
IDWriteFactory *factory = 0;
|
|
error = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), (IUnknown**)&factory);
|
|
DeferRelease(factory);
|
|
DWCheckPtr(error, factory, assert(!"factory"));
|
|
|
|
// File
|
|
IDWriteFontFile *font_file = 0;
|
|
error = factory->CreateFontFileReference(font_path, 0, &font_file);
|
|
DeferRelease(font_file);
|
|
DWCheckPtr(error, font_file, assert(!"font file"));
|
|
|
|
// Face
|
|
error = factory->CreateFontFace(DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &font_file, 0, DWRITE_FONT_SIMULATIONS_NONE, &font.face);
|
|
// We don't use DeferRelease because we intend to keep the font face around after the baking process.
|
|
DWCheckPtr(error, font.face, assert(!"font face"));
|
|
|
|
// Params
|
|
IDWriteRenderingParams *default_rendering_params = 0;
|
|
error = factory->CreateRenderingParams(&default_rendering_params);
|
|
DeferRelease(default_rendering_params);
|
|
DWCheckPtr(error, default_rendering_params, assert(!"rendering params"));
|
|
|
|
FLOAT gamma = 1.f;
|
|
|
|
IDWriteRenderingParams *rendering_params = 0;
|
|
error = factory->CreateCustomRenderingParams(gamma,
|
|
default_rendering_params->GetEnhancedContrast(),
|
|
default_rendering_params->GetClearTypeLevel(),
|
|
default_rendering_params->GetPixelGeometry(),
|
|
DWRITE_RENDERING_MODE_NATURAL,
|
|
&rendering_params);
|
|
DeferRelease(rendering_params);
|
|
DWCheckPtr(error, rendering_params, assert(!"rendering params"));
|
|
|
|
// Interop
|
|
IDWriteGdiInterop *dwrite_gdi_interop = 0;
|
|
error = factory->GetGdiInterop(&dwrite_gdi_interop);
|
|
DeferRelease(dwrite_gdi_interop);
|
|
DWCheckPtr(error, dwrite_gdi_interop, assert(!"gdi interop"));
|
|
|
|
// Metrics
|
|
DWRITE_FONT_METRICS font_metrics = {0};
|
|
font.face->GetMetrics(&font_metrics);
|
|
|
|
float pixel_per_em = point_size*(1.f/72.f)*dpi;
|
|
float pixel_per_design_unit = pixel_per_em/((float)font_metrics.designUnitsPerEm);
|
|
|
|
int32_t raster_target_w = (int32_t)(8.f*((float)font_metrics.capHeight)*pixel_per_design_unit);
|
|
int32_t raster_target_h = (int32_t)(8.f*((float)font_metrics.capHeight)*pixel_per_design_unit);
|
|
float raster_target_x = (float)(raster_target_w/2);
|
|
float raster_target_y = (float)(raster_target_h/2);
|
|
|
|
assert((float) ((int)(raster_target_x)) == raster_target_x);
|
|
assert((float) ((int)(raster_target_y)) == raster_target_y);
|
|
|
|
// Glyph Count
|
|
font.glyph_count = font.face->GetGlyphCount();
|
|
|
|
// Render Target
|
|
IDWriteBitmapRenderTarget *render_target = 0;
|
|
error = dwrite_gdi_interop->CreateBitmapRenderTarget(0, raster_target_w, raster_target_h, &render_target);
|
|
HDC dc = render_target->GetMemoryDC();
|
|
|
|
// Clear the Render Target
|
|
{
|
|
HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN));
|
|
SetDCPenColor(dc, back_color);
|
|
SelectObject(dc, GetStockObject(DC_BRUSH));
|
|
SetDCBrushColor(dc, back_color);
|
|
Rectangle(dc, 0, 0, raster_target_w, raster_target_h);
|
|
SelectObject(dc, original);
|
|
}
|
|
|
|
// Allocate CPU Side Atlas
|
|
int32_t atlas_w = 4*(int32_t)(((float)font_metrics.capHeight)*pixel_per_design_unit);
|
|
int32_t atlas_h = 4*(int32_t)(((float)font_metrics.capHeight)*pixel_per_design_unit);
|
|
if (atlas_w < 16){
|
|
atlas_w = 16;
|
|
}
|
|
else{
|
|
atlas_w = next_power_of_two(atlas_w);
|
|
}
|
|
if (atlas_h < 256){
|
|
atlas_h = 256;
|
|
}
|
|
else{
|
|
atlas_h = next_power_of_two(atlas_h);
|
|
}
|
|
int32_t atlas_c = (font.glyph_count + 3)/4;
|
|
int32_t atlas_slice_size = atlas_w*atlas_h*3;
|
|
int32_t atlas_memory_size = atlas_slice_size*atlas_c;
|
|
uint8_t *atlas_memory = (uint8_t*)malloc(atlas_memory_size);
|
|
memset(atlas_memory, 0, atlas_memory_size);
|
|
|
|
// Allocate the Metric Data
|
|
font.metrics = (Glyph_Metrics*)malloc(sizeof(Glyph_Metrics)*font.glyph_count);
|
|
memset(font.metrics, 0, sizeof(Glyph_Metrics)*font.glyph_count);
|
|
|
|
// Fill the CPU Side Atlas and Metric Data
|
|
for (uint16_t glyph_index = 0; glyph_index < font.glyph_count; glyph_index += 1){
|
|
// Render the Glyph Into the Target
|
|
DWRITE_GLYPH_RUN glyph_run = {0};
|
|
glyph_run.fontFace = font.face;
|
|
glyph_run.fontEmSize = pixel_per_em;
|
|
glyph_run.glyphCount = 1;
|
|
glyph_run.glyphIndices = &glyph_index;
|
|
RECT bounding_box = {0};
|
|
error = render_target->DrawGlyphRun(raster_target_x, raster_target_y,
|
|
DWRITE_MEASURING_MODE_NATURAL, &glyph_run, rendering_params, RGB(255, 255, 255), &bounding_box);
|
|
DWCheck(error, continue);
|
|
|
|
assert(0 <= bounding_box.left);
|
|
assert(0 <= bounding_box.top);
|
|
assert(bounding_box.right <= raster_target_w);
|
|
assert(bounding_box.bottom <= raster_target_h);
|
|
|
|
// Compute Our Glyph Metrics
|
|
DWRITE_GLYPH_METRICS glyph_metrics = {0};
|
|
error = font.face->GetDesignGlyphMetrics(&glyph_index, 1, &glyph_metrics, false);
|
|
DWCheck(error, continue);
|
|
|
|
float off_x = (float)bounding_box.left - raster_target_x;
|
|
float off_y = (float)bounding_box.top - raster_target_y;
|
|
float advance = ((float)glyph_metrics.advanceWidth)*pixel_per_design_unit;
|
|
int32_t tex_w = bounding_box.right - bounding_box.left;
|
|
int32_t tex_h = bounding_box.bottom - bounding_box.top;
|
|
|
|
font.metrics[glyph_index].off_x = off_x;
|
|
font.metrics[glyph_index].off_y = off_y;
|
|
font.metrics[glyph_index].advance = (float)round_up(advance);
|
|
font.metrics[glyph_index].xy_w = (float)tex_w;
|
|
font.metrics[glyph_index].xy_h = (float)tex_h;
|
|
font.metrics[glyph_index].uv_w = (float)tex_w/(float)atlas_w;
|
|
font.metrics[glyph_index].uv_h = (float)tex_h/(float)atlas_h;
|
|
|
|
// Get the Bitmap
|
|
HBITMAP bitmap = (HBITMAP)GetCurrentObject(dc, OBJ_BITMAP);
|
|
DIBSECTION dib = {0};
|
|
GetObject(bitmap, sizeof(dib), &dib);
|
|
|
|
// Blit the Bitmap Into Our CPU Side Atlas
|
|
int32_t x_slice_offset = (3*atlas_w/2)*(glyph_index&1);
|
|
int32_t y_slice_offset = (3*atlas_w*atlas_h/2)*((glyph_index&2) >> 1);
|
|
uint8_t *atlas_slice = atlas_memory + atlas_slice_size*(glyph_index/4) + x_slice_offset + y_slice_offset;
|
|
|
|
{
|
|
assert(dib.dsBm.bmBitsPixel == 32);
|
|
int32_t in_pitch = dib.dsBm.bmWidthBytes;
|
|
int32_t out_pitch = atlas_w*3;
|
|
uint8_t *in_line = (uint8_t*)dib.dsBm.bmBits + bounding_box.left*4 + bounding_box.top*in_pitch;
|
|
uint8_t *out_line = atlas_slice;
|
|
for (int32_t y = 0; y < tex_h; y += 1){
|
|
uint8_t *in_pixel = in_line;
|
|
uint8_t *out_pixel = out_line;
|
|
for (int32_t x = 0; x < tex_w; x += 1){
|
|
out_pixel[0] = in_pixel[2];
|
|
out_pixel[1] = in_pixel[1];
|
|
out_pixel[2] = in_pixel[0];
|
|
in_pixel += 4;
|
|
out_pixel += 3;
|
|
}
|
|
in_line += in_pitch;
|
|
out_line += out_pitch;
|
|
}
|
|
}
|
|
|
|
// Clear the Render Target
|
|
{
|
|
HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN));
|
|
SetDCPenColor(dc, back_color);
|
|
SelectObject(dc, GetStockObject(DC_BRUSH));
|
|
SetDCBrushColor(dc, back_color);
|
|
Rectangle(dc,
|
|
bounding_box.left, bounding_box.top,
|
|
bounding_box.right, bounding_box.bottom);
|
|
SelectObject(dc, original);
|
|
}
|
|
}
|
|
|
|
// Allocate and Fill the GPU Side Atlas
|
|
glGenTextures(1, &font.texture);
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, font.texture);
|
|
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGB, atlas_w, atlas_h, atlas_c, 0, GL_RGB, GL_UNSIGNED_BYTE, atlas_memory);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
// Free CPU Side Atlas
|
|
free(atlas_memory);
|
|
atlas_memory = 0;
|
|
}
|
|
|
|
int32_t mode = 0;
|
|
bool32 paused = false;
|
|
for (;;){
|
|
MSG msg = {0};
|
|
for (;PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);){
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
if (msg.message == WM_KEYDOWN){
|
|
if (msg.wParam == VK_SPACE){
|
|
// Check if this key just got pressed
|
|
if (((msg.lParam >> 30) & 1) == 0){
|
|
paused = !paused;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
HDC dc = GetDC(wnd);
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
|
|
|
|
enum{
|
|
TB_Black,
|
|
TB_White,
|
|
TB_Red,
|
|
TB_Green,
|
|
TB_Blue,
|
|
TB_Yellow,
|
|
TB_Cyan,
|
|
TB_Purple,
|
|
TB_COUNT,
|
|
};
|
|
|
|
enum{
|
|
TF_Gray,
|
|
TF_RGB,
|
|
TF_YCP,
|
|
TF_AlphaGray,
|
|
TF_AlphaRGB,
|
|
TF_AlphaYCP,
|
|
TF_COUNT,
|
|
};
|
|
|
|
int32_t mode_index = mode/16;
|
|
int32_t bmode = (mode_index/TF_COUNT)%TB_COUNT;
|
|
int32_t fmode = mode_index%TF_COUNT;
|
|
|
|
float pop_r = 0.f;
|
|
float pop_g = 0.f;
|
|
float pop_b = 0.f;
|
|
#define SetPopColor(r,g,b) pop_r = (r), pop_g = (g), pop_b = (b)
|
|
switch (bmode){
|
|
case TB_Black:
|
|
{
|
|
glClearColor(0.f, 0.f, 0.f, 1.f);
|
|
SetPopColor(1.f, 1.f, 1.f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
draw_string(font, "Back = Black", 300, 60, pop_r, pop_g, pop_b, 1.f);
|
|
}break;
|
|
|
|
case TB_White:
|
|
{
|
|
glClearColor(1.f, 1.f, 1.f, 1.f);
|
|
SetPopColor(0.f, 0.f, 0.f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
draw_string(font, "Back = White", 300, 60, pop_r, pop_g, pop_b, 1.f);
|
|
}break;
|
|
|
|
case TB_Red:
|
|
{
|
|
glClearColor(0.5f, 0.f, 0.f, 1.f);
|
|
SetPopColor(0.f, 0.5f, 0.5f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
draw_string(font, "Back = (.5,0,0)", 300, 60, pop_r, pop_g, pop_b, 1.f);
|
|
}break;
|
|
|
|
case TB_Green:
|
|
{
|
|
glClearColor(0.f, 0.5f, 0.f, 1.f);
|
|
SetPopColor(0.5f, 0.f, 0.5f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
draw_string(font, "Back = (0,.5,0)", 300, 60, pop_r, pop_g, pop_b, 1.f);
|
|
}break;
|
|
|
|
case TB_Blue:
|
|
{
|
|
glClearColor(0.f, 0.f, 0.5f, 1.f);
|
|
SetPopColor(0.5f, 0.5f, 0.f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
draw_string(font, "Back = (0,0,.5)", 300, 60, pop_r, pop_g, pop_b, 1.f);
|
|
}break;
|
|
|
|
case TB_Yellow:
|
|
{
|
|
glClearColor(0.5f, 0.5f, 0.f, 1.f);
|
|
SetPopColor(0.f, 0.f, 0.5f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
draw_string(font, "Back = (.5,.5,0)", 300, 60, pop_r, pop_g, pop_b, 1.f);
|
|
}break;
|
|
|
|
case TB_Cyan:
|
|
{
|
|
glClearColor(0.f, 0.5f, 0.5f, 1.f);
|
|
SetPopColor(0.5f, 0.f, 0.f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
draw_string(font, "Back = (0,.5,.5)", 300, 60, pop_r, pop_g, pop_b, 1.f);
|
|
}break;
|
|
|
|
case TB_Purple:
|
|
{
|
|
glClearColor(0.5f, 0.f, 0.5f, 1.f);
|
|
SetPopColor(0.f, 0.5f, 0.f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
draw_string(font, "Back = (.5,0,.5)", 300, 60, pop_r, pop_g, pop_b, 1.f);
|
|
}break;
|
|
}
|
|
|
|
switch (fmode){
|
|
case TF_Gray:
|
|
{
|
|
for (int32_t i = 1; i <= 6; i += 1){
|
|
float v = (i - 1)/5.f;
|
|
draw_string(font, "DirectWrite rasterizer testing", 50, i*80 + 40, v, v, v, 1.f);
|
|
}
|
|
draw_string(font, "Fore = Grays", 550, 60, pop_r, pop_g, pop_b, 1.f);
|
|
}break;
|
|
|
|
case TF_RGB:
|
|
{
|
|
for (int32_t i = 0; i < 3; i += 1){
|
|
float v[3] = {0.f, 0.f, 0.f};
|
|
v[i] = 0.5f;
|
|
draw_string(font, "DirectWrite rasterizer testing", 50 + 250*i, 120, v[0], v[1], v[2], 1.f);
|
|
}
|
|
draw_string(font, "Fore = Red Green Blue", 550, 60, pop_r, pop_g, pop_b, 1.f);
|
|
}break;
|
|
|
|
case TF_YCP:
|
|
{
|
|
for (int32_t i = 0; i < 3; i += 1){
|
|
float v[3] = {0.5f, 0.5f, 0.5f};
|
|
v[(i + 2)%3] = 0.f;
|
|
draw_string(font, "DirectWrite rasterizer testing", 50 + 250*i, 120, v[0], v[1], v[2], 1.f);
|
|
}
|
|
draw_string(font, "Fore = Yellow Cyan Purple", 550, 60, pop_r, pop_g, pop_b, 1.f);
|
|
}break;
|
|
|
|
case TF_AlphaGray:
|
|
{
|
|
for (int32_t j = 1; j <= 6; j += 1){
|
|
float a = (j - 1)/5.f;
|
|
draw_string(font, "DirectWrite rasterizer testing", 50, j*80 + 40, 1.f, 1.f, 1.f, a);
|
|
draw_string(font, "DirectWrite rasterizer testing", 300, j*80 + 40, 0.f, 0.f, 0.f, a);
|
|
}
|
|
draw_string(font, "Fore = Alpha Black and White", 550, 60, pop_r, pop_g, pop_b, 1.f);
|
|
}break;
|
|
|
|
case TF_AlphaRGB:
|
|
{
|
|
for (int32_t j = 1; j <= 6; j += 1){
|
|
float a = (j - 1)/5.f;
|
|
for (int32_t i = 0; i < 3; i += 1){
|
|
float v[3] = {0.f, 0.f, 0.f};
|
|
v[i] = 0.5f;
|
|
draw_string(font, "DirectWrite rasterizer testing", 50 + 250*i, j*80 + 40, v[0], v[1], v[2], a);
|
|
}
|
|
}
|
|
draw_string(font, "Fore = Alpha Red Green Blue", 550, 60, pop_r, pop_g, pop_b, 1.f);
|
|
}break;
|
|
|
|
case TF_AlphaYCP:
|
|
{
|
|
for (int32_t j = 1; j <= 6; j += 1){
|
|
float a = (j - 1)/5.f;
|
|
for (int32_t i = 0; i < 3; i += 1){
|
|
float v[3] = {0.5f, 0.5f, 0.5f};
|
|
v[(i + 2)%3] = 0.f;
|
|
draw_string(font, "DirectWrite rasterizer testing", 50 + 250*i, j*80 + 40, v[0], v[1], v[2], a);
|
|
}
|
|
}
|
|
draw_string(font, "Fore = Alpha Yellow Cyan Purple", 550, 60, pop_r, pop_g, pop_b, 1.f);
|
|
}break;
|
|
}
|
|
|
|
if (!paused){
|
|
draw_string(font, "Press space to pause cycle", 50, 60, pop_r, pop_g, pop_b, 1.f);
|
|
}
|
|
else{
|
|
draw_string(font, "Press space to resume cycle", 50, 60, pop_r, pop_g, pop_b, 1.f);
|
|
}
|
|
|
|
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
|
|
glBlitFramebuffer(0, 0, window_width, window_height, 0, 0, window_width, window_height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
|
|
|
SwapBuffers(dc);
|
|
|
|
Sleep(100);
|
|
if (!paused){
|
|
mode += 1;
|
|
}
|
|
ShowWindow(wnd, TRUE);
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
////////////////////////////////
|
|
|
|
#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000
|
|
#define WGL_DRAW_TO_WINDOW_ARB 0x2001
|
|
#define WGL_DRAW_TO_BITMAP_ARB 0x2002
|
|
#define WGL_ACCELERATION_ARB 0x2003
|
|
#define WGL_NEED_PALETTE_ARB 0x2004
|
|
#define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005
|
|
#define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006
|
|
#define WGL_SWAP_METHOD_ARB 0x2007
|
|
#define WGL_NUMBER_OVERLAYS_ARB 0x2008
|
|
#define WGL_NUMBER_UNDERLAYS_ARB 0x2009
|
|
#define WGL_TRANSPARENT_ARB 0x200A
|
|
#define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037
|
|
#define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038
|
|
#define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039
|
|
#define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A
|
|
#define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B
|
|
#define WGL_SHARE_DEPTH_ARB 0x200C
|
|
#define WGL_SHARE_STENCIL_ARB 0x200D
|
|
#define WGL_SHARE_ACCUM_ARB 0x200E
|
|
#define WGL_SUPPORT_GDI_ARB 0x200F
|
|
#define WGL_SUPPORT_OPENGL_ARB 0x2010
|
|
#define WGL_DOUBLE_BUFFER_ARB 0x2011
|
|
#define WGL_STEREO_ARB 0x2012
|
|
#define WGL_PIXEL_TYPE_ARB 0x2013
|
|
#define WGL_COLOR_BITS_ARB 0x2014
|
|
#define WGL_RED_BITS_ARB 0x2015
|
|
#define WGL_RED_SHIFT_ARB 0x2016
|
|
#define WGL_GREEN_BITS_ARB 0x2017
|
|
#define WGL_GREEN_SHIFT_ARB 0x2018
|
|
#define WGL_BLUE_BITS_ARB 0x2019
|
|
#define WGL_BLUE_SHIFT_ARB 0x201A
|
|
#define WGL_ALPHA_BITS_ARB 0x201B
|
|
#define WGL_ALPHA_SHIFT_ARB 0x201C
|
|
#define WGL_ACCUM_BITS_ARB 0x201D
|
|
#define WGL_ACCUM_RED_BITS_ARB 0x201E
|
|
#define WGL_ACCUM_GREEN_BITS_ARB 0x201F
|
|
#define WGL_ACCUM_BLUE_BITS_ARB 0x2020
|
|
#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021
|
|
#define WGL_DEPTH_BITS_ARB 0x2022
|
|
#define WGL_STENCIL_BITS_ARB 0x2023
|
|
#define WGL_AUX_BUFFERS_ARB 0x2024
|
|
|
|
#define WGL_NO_ACCELERATION_ARB 0x2025
|
|
#define WGL_GENERIC_ACCELERATION_ARB 0x2026
|
|
#define WGL_FULL_ACCELERATION_ARB 0x2027
|
|
|
|
#define WGL_SWAP_EXCHANGE_ARB 0x2028
|
|
#define WGL_SWAP_COPY_ARB 0x2029
|
|
#define WGL_SWAP_UNDEFINED_ARB 0x202A
|
|
|
|
#define WGL_TYPE_RGBA_ARB 0x202B
|
|
#define WGL_TYPE_COLORINDEX_ARB 0x202C
|
|
|
|
#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091
|
|
#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092
|
|
#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093
|
|
#define WGL_CONTEXT_FLAGS_ARB 0x2094
|
|
#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126
|
|
|
|
#define WGL_CONTEXT_DEBUG_BIT_ARB 0x0001
|
|
#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002
|
|
|
|
#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001
|
|
#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002
|
|
|
|
#define ERROR_INVALID_VERSION_ARB 0x2095
|
|
#define ERROR_INVALID_PROFILE_ARB 0x2096
|
|
|
|
static void*
|
|
get_procedure(char *name){
|
|
void *result = wglGetProcAddress(name);
|
|
return(result);
|
|
}
|
|
|
|
typedef HGLRC wglCreateContextAttribsARB_Function(HDC hDC,
|
|
HGLRC hShareContext,
|
|
const int *attribList);
|
|
|
|
typedef BOOL wglChoosePixelFormatARB_Function(HDC hdc,
|
|
const int *piAttribIList,
|
|
const FLOAT *pfAttribFList,
|
|
UINT nMaxFormats,
|
|
int *piFormats,
|
|
UINT *nNumFormats);
|
|
|
|
LRESULT
|
|
window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){
|
|
LRESULT result = 0;
|
|
switch (uMsg){
|
|
case WM_CREATE:
|
|
{}break;
|
|
|
|
case WM_CLOSE:
|
|
case WM_DESTROY:
|
|
{
|
|
ExitProcess(0);
|
|
}break;
|
|
|
|
default:
|
|
{
|
|
result = DefWindowProc(hwnd, uMsg, wParam, lParam);
|
|
}break;
|
|
}
|
|
return(result);
|
|
}
|
|
|
|
HWND
|
|
window_setup(HINSTANCE hInstance){
|
|
#define L_STARTER_WINDOW_CLASS_NAME L"starter-window"
|
|
#define L_REAL_WINDOW_CLASS_NAME L"window"
|
|
wchar_t title[] = L"Example DirectWrite Based Rasterizer";
|
|
|
|
// NOTE(allen): Setup a starter window
|
|
WNDCLASSEX starter_window_class = {0};
|
|
starter_window_class.cbSize = sizeof(starter_window_class);
|
|
starter_window_class.lpfnWndProc = DefWindowProc;
|
|
starter_window_class.hInstance = hInstance;
|
|
starter_window_class.lpszClassName = L_STARTER_WINDOW_CLASS_NAME;
|
|
ATOM starter_window_class_atom = RegisterClassEx(&starter_window_class);
|
|
assert(starter_window_class_atom != 0);
|
|
|
|
HWND starter_window = CreateWindowEx(0, L_STARTER_WINDOW_CLASS_NAME, L"",
|
|
0,
|
|
CW_USEDEFAULT, CW_USEDEFAULT,
|
|
CW_USEDEFAULT, CW_USEDEFAULT,
|
|
NULL,
|
|
NULL,
|
|
hInstance,
|
|
0);
|
|
|
|
assert(starter_window != 0);
|
|
|
|
HDC hdc = GetDC(starter_window);
|
|
assert(hdc != 0);
|
|
|
|
PIXELFORMATDESCRIPTOR px_format = {0};
|
|
px_format.nSize = sizeof(px_format);
|
|
px_format.nVersion = 1;
|
|
px_format.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
|
|
px_format.iPixelType = PFD_TYPE_RGBA;
|
|
px_format.cColorBits = 24;
|
|
|
|
int32_t pixel_format_index = ChoosePixelFormat(hdc, &px_format);
|
|
assert(pixel_format_index != 0);
|
|
|
|
bool32 success = DescribePixelFormat(hdc, pixel_format_index, sizeof(px_format), &px_format);
|
|
assert(success);
|
|
success = SetPixelFormat(hdc, pixel_format_index, &px_format);
|
|
assert(success);
|
|
|
|
HGLRC starter_context = wglCreateContext(hdc);
|
|
assert(starter_context != 0);
|
|
|
|
wglMakeCurrent(hdc, starter_context);
|
|
|
|
// NOTE(allen): Get extensions from the starter window
|
|
wglCreateContextAttribsARB_Function *wglCreateContextAttribsARB = (wglCreateContextAttribsARB_Function*)get_procedure("wglCreateContextAttribsARB");
|
|
|
|
wglChoosePixelFormatARB_Function *wglChoosePixelFormatARB = (wglChoosePixelFormatARB_Function*)get_procedure("wglChoosePixelFormatARB");
|
|
|
|
// NOTE(allen): Setup the real window
|
|
WNDCLASSEX real_window_class = {0};
|
|
real_window_class.cbSize = sizeof(real_window_class);
|
|
real_window_class.style = CS_HREDRAW | CS_VREDRAW;
|
|
real_window_class.lpfnWndProc = window_proc;
|
|
real_window_class.hInstance = hInstance;
|
|
real_window_class.hIcon = LoadIcon(NULL, IDI_APPLICATION);
|
|
real_window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
real_window_class.lpszClassName = L_REAL_WINDOW_CLASS_NAME;
|
|
real_window_class.hIconSm = NULL;
|
|
ATOM real_window_class_atom = RegisterClassEx(&real_window_class);
|
|
assert(real_window_class_atom != 0);
|
|
|
|
RECT window_rect = {0, 0, window_width, window_height};
|
|
AdjustWindowRect(&window_rect, WS_OVERLAPPED, FALSE);
|
|
|
|
uint32_t real_style = WS_OVERLAPPEDWINDOW;
|
|
HWND real_window = CreateWindowEx(0, L_REAL_WINDOW_CLASS_NAME, (wchar_t*)title,
|
|
real_style,
|
|
CW_USEDEFAULT, CW_USEDEFAULT,
|
|
window_rect.right - window_rect.left,
|
|
window_rect.bottom - window_rect.top,
|
|
NULL,
|
|
NULL,
|
|
hInstance,
|
|
0);
|
|
|
|
DWORD error_code = GetLastError();
|
|
|
|
assert(real_window != 0);
|
|
|
|
HDC real_hdc = GetDC(real_window);
|
|
assert(real_hdc != 0);
|
|
|
|
int32_t px_format_attributes[] = {
|
|
WGL_DRAW_TO_WINDOW_ARB, TRUE,
|
|
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
|
|
WGL_SUPPORT_OPENGL_ARB, TRUE,
|
|
WGL_DOUBLE_BUFFER_ARB, TRUE,
|
|
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
|
|
0,
|
|
};
|
|
int32_t real_pixel_format_index = 0;
|
|
uint32_t number_of_formats = 0;
|
|
success = wglChoosePixelFormatARB(hdc, px_format_attributes, 0,
|
|
1, &real_pixel_format_index, &number_of_formats);
|
|
assert(success);
|
|
assert(number_of_formats != 0);
|
|
|
|
PIXELFORMATDESCRIPTOR real_px_format = {0};
|
|
DescribePixelFormat(hdc, real_pixel_format_index, sizeof(real_px_format), &real_px_format);
|
|
success = SetPixelFormat(real_hdc, real_pixel_format_index, &real_px_format);
|
|
assert(success);
|
|
|
|
int32_t context_attributes[] = {
|
|
WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
|
|
WGL_CONTEXT_MINOR_VERSION_ARB, 0,
|
|
WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB,
|
|
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
|
|
0,
|
|
};
|
|
HGLRC real_context = wglCreateContextAttribsARB(real_hdc, 0, context_attributes);
|
|
assert(real_context != 0);
|
|
wglMakeCurrent(real_hdc, real_context);
|
|
|
|
ReleaseDC(real_window, real_hdc);
|
|
|
|
// NOTE(allen): Close the starter window
|
|
ReleaseDC(starter_window, hdc);
|
|
success = wglDeleteContext(starter_context);
|
|
assert(success);
|
|
DestroyWindow(starter_window);
|
|
|
|
// NOTE(allen): Load functions
|
|
#define GL_FUNC(N,R,P) N = (N##_Type*)get_procedure(#N);
|
|
#include "example_gl_funcs.h"
|
|
#define GL_FUNC(N,R,P) assert(N != 0);
|
|
#include "example_gl_funcs.h"
|
|
|
|
return(real_window);
|
|
}
|
|
|