Skip to content

Instantly share code, notes, and snippets.

@bens
Created May 10, 2020 14:36
Show Gist options
  • Save bens/767a26f0665d206b5f3ac9c3d516d0b9 to your computer and use it in GitHub Desktop.
Save bens/767a26f0665d206b5f3ac9c3d516d0b9 to your computer and use it in GitHub Desktop.
Hello World for OpenGL 3.3 with SDL2 in ziglang
const std = @import("std");
const Allocator = std.mem.Allocator;
const c = @cImport({
@cInclude("./gl_core_3_3.h");
@cInclude("SDL2/SDL.h");
@cInclude("SDL2/SDL_opengl.h");
});
const SDLError = error{
FailedInit,
FailedCreatingWindow,
FailedCreateGLContext,
FailedGettingEvent,
FailedDraw,
FailedScreenUpdate,
FailedSettingAttribute,
FailedSettingSwapInterval,
};
const GLError = error{
FailedCreateProgram,
FailedCreateShader,
FailedLinkingProgram,
FailedCompilingShader,
FailedGettingAttributeLocation,
};
const vertexShaderSource: [*:0]const u8 =
\\#version 140
\\in vec2 LVertexPos2D;
\\void main() {
\\ gl_Position = vec4( LVertexPos2D.x, LVertexPos2D.y, 0, 1 );
\\}
;
const fragmentShaderSource: [*:0]const u8 =
\\#version 140
\\out vec4 LFragment;
\\void main() {
\\ LFragment = vec4( 1.0, 1.0, 1.0, 1.0 );
\\}
;
const System = struct {
window: *c.SDL_Window,
glContext: c.SDL_GLContext,
glVBO: c.GLuint,
glIBO: c.GLuint,
glProgram: c_uint,
glVertexShader: c_uint,
glFragmentShader: c_uint,
vertexPos2DLocation: c_uint,
fn render(self: System) SDLError!void {
c.glClear(c.GL_COLOR_BUFFER_BIT);
c.glUseProgram(self.glProgram);
c.glEnableVertexAttribArray(self.vertexPos2DLocation);
//Set vertex data
c.glBindBuffer(c.GL_ARRAY_BUFFER, self.glVBO);
c.glVertexAttribPointer(
self.vertexPos2DLocation,
2,
c.GL_FLOAT,
c.GL_FALSE,
2 * @sizeOf(c.GLfloat),
null,
);
c.glBindBuffer(c.GL_ELEMENT_ARRAY_BUFFER, self.glIBO);
c.glDrawElements(c.GL_TRIANGLE_FAN, 4, c.GL_UNSIGNED_INT, null);
c.glDisableVertexAttribArray(self.vertexPos2DLocation);
c.glUseProgram(0);
c.SDL_GL_SwapWindow(self.window);
}
};
var logBuffer: [4096]u8 = undefined;
pub fn main() !void {
var logAlloc = std.heap.FixedBufferAllocator.init(&logBuffer);
const system = try initialise(&logAlloc.allocator);
defer deinitialise(system);
const surface = c.SDL_GetWindowSurface(system.window);
// Loop, blocking for events.
var event: c.SDL_Event = undefined;
while (true) {
if (c.SDL_WaitEvent(&event) == 0) {
std.debug.warn(
"Getting next event failed: {s}\n",
.{c.SDL_GetError()},
);
return SDLError.FailedGettingEvent;
}
if (is_quit(&event)) break;
try system.render();
}
}
fn initialise(logAlloc: *Allocator) !System {
try SDL.init(c.SDL_INIT_VIDEO | c.SDL_INIT_EVENTS);
errdefer SDL.quit();
try SDL.setGLAttribute(c.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
try SDL.setGLAttribute(c.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, 1);
try SDL.setGLAttribute(
c.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK,
c.SDL_GL_CONTEXT_PROFILE_CORE,
);
const window = try SDL.createWindow(
"SDL2 & OGL3.1",
640,
480,
c.SDL_WINDOW_OPENGL | c.SDL_WINDOW_SHOWN,
);
const glctx = try SDL.createGLContext(window);
errdefer SDL.deleteGLContext(glctx);
const program = try GL.createProgram();
errdefer GL.deleteProgram(program);
const vShader = try GL.createVertexShader();
errdefer GL.deleteShader(vShader);
c.glShaderSource(vShader, 1, &vertexShaderSource, null);
try GL.compileShader(logAlloc, vShader);
c.glAttachShader(program, vShader);
const fShader = try GL.createFragmentShader();
errdefer GL.deleteShader(fShader);
c.glShaderSource(fShader, 1, &fragmentShaderSource, null);
try GL.compileShader(logAlloc, fShader);
c.glAttachShader(program, fShader);
try GL.linkProgram(logAlloc, program);
errdefer GL.deleteShader(vShader);
const gVertexPos2DLocation = c.glGetAttribLocation(program, "LVertexPos2D");
if (gVertexPos2DLocation == -1) {
std.debug.warn(
"LVertexPos2D is not a valid GLSL program variable!\n",
.{},
);
return GLError.FailedGettingAttributeLocation;
}
std.debug.warn("Pos2d location: {}\n", .{gVertexPos2DLocation});
//Create VBO
const vertexData = [_]c.GLfloat{
-0.5, -0.5,
0.5, -0.5,
0.5, 0.5,
-0.5, 0.5,
};
var vbo: c.GLuint = undefined;
c.glGenBuffers(1, &vbo);
c.glBindBuffer(c.GL_ARRAY_BUFFER, vbo);
c.glBufferData(
c.GL_ARRAY_BUFFER,
2 * 4 * @sizeOf(c.GLfloat),
&vertexData,
c.GL_STATIC_DRAW,
);
//Create IBO
const indexData = [_]c.GLuint{ 0, 1, 2, 3 };
var ibo: c.GLuint = undefined;
c.glGenBuffers(1, &ibo);
c.glBindBuffer(c.GL_ELEMENT_ARRAY_BUFFER, ibo);
c.glBufferData(
c.GL_ELEMENT_ARRAY_BUFFER,
4 * @sizeOf(c.GLuint),
&indexData,
c.GL_STATIC_DRAW,
);
try SDL.setSwapInterval(1);
c.SDL_SetEventFilter(event_filter, null);
return System{
.window = window,
.glContext = glctx,
.glVBO = vbo,
.glIBO = ibo,
.glProgram = program,
.glVertexShader = vShader,
.glFragmentShader = fShader,
.vertexPos2DLocation = @intCast(c_uint, gVertexPos2DLocation),
};
}
fn deinitialise(system: System) void {
defer SDL.quit();
defer SDL.deleteGLContext(system.glContext);
defer GL.deleteProgram(system.glProgram);
defer GL.deleteShader(system.glVertexShader);
defer GL.deleteShader(system.glFragmentShader);
}
// *************
// ** HELPERS **
// *************
/// Pressing 'Q' stops the program.
fn is_quit(event: *c.SDL_Event) bool {
return switch (event.type) {
c.SDL_QUIT => true,
c.SDL_KEYDOWN => event.key.keysym.sym == c.SDLK_q,
else => false,
};
}
/// A filter function to only enqueue certain events.
export fn event_filter(userdata: ?*c_void, event: [*c]c.SDL_Event) c_int {
return switch (event.*.type) {
c.SDL_QUIT, c.SDL_KEYDOWN, c.SDL_WINDOWEVENT => 1,
else => 0,
};
}
// **************
// ** WRAPPERS **
// **************
/// SDL Wrapper
const SDL = struct {
fn init(flags: u32) SDLError!void {
if (c.SDL_Init(flags) != 0) {
std.debug.warn(
"Initialising SDL failed: {s}\n",
.{c.SDL_GetError()},
);
return SDLError.FailedInit;
}
}
fn quit() void {
c.SDL_Quit();
}
fn createWindow(
title: [*c]const u8,
w: c_int,
h: c_int,
flags: u32,
) SDLError!*c.SDL_Window {
const try_window = c.SDL_CreateWindow(
title,
c.SDL_WINDOWPOS_UNDEFINED,
c.SDL_WINDOWPOS_UNDEFINED,
w,
h,
flags,
);
if (try_window) |window| {
return window;
} else {
std.debug.warn(
"Creating Window failed: {s}\n",
.{c.SDL_GetError()},
);
return SDLError.FailedCreatingWindow;
}
}
fn createGLContext(window: *c.SDL_Window) SDLError!c.SDL_GLContext {
if (c.SDL_GL_CreateContext(window)) |glctx| {
return glctx;
} else {
std.debug.warn(
"Creating OpenGL context failed: {s}\n",
.{c.SDL_GetError()},
);
return SDLError.FailedCreateGLContext;
}
}
fn deleteGLContext(glctx: c.SDL_GLContext) void {
c.SDL_GL_DeleteContext(glctx);
}
fn setGLAttribute(attr: c.SDL_GLattr, value: c_int) SDLError!void {
const r = c.SDL_GL_SetAttribute(attr, value);
if (r != 0) {
std.debug.warn(
"Setting attribute {} failed: {s}\n",
.{ attr, c.SDL_GetError() },
);
return SDLError.FailedSettingAttribute;
} else {
return;
}
}
fn setSwapInterval(interval: c_int) SDLError!void {
if (c.SDL_GL_SetSwapInterval(interval) < 0) {
std.debug.warn(
"Set swap interval failed: {s}\n",
.{c.SDL_GetError()},
);
return SDLError.FailedSettingSwapInterval;
}
}
};
/// OpenGL Wrapper
const GL = struct {
fn createProgram() GLError!c_uint {
const r = c.glCreateProgram();
if (r == 0) {
return GLError.FailedCreateProgram;
} else {
return r;
}
}
fn deleteProgram(program: c_uint) void {
c.glDeleteProgram(program);
}
fn createVertexShader() GLError!c_uint {
const r = c.glCreateShader(c.GL_VERTEX_SHADER);
if (r == 0) {
return GLError.FailedCreateShader;
} else {
return r;
}
}
fn createFragmentShader() GLError!c_uint {
const r = c.glCreateShader(c.GL_FRAGMENT_SHADER);
if (r == 0) {
return GLError.FailedCreateShader;
} else {
return r;
}
}
fn deleteShader(shader: c_uint) void {
c.glDeleteShader(shader);
}
fn compileShader(logAlloc: *Allocator, shader: c_uint) !void {
c.glCompileShader(shader);
var ok: c_int = undefined;
c.glGetShaderiv(shader, c.GL_COMPILE_STATUS, &ok);
if (ok == c.GL_FALSE) {
std.debug.warn("Unable to compile shader: {}\n", .{shader});
try GL.printShaderLog(logAlloc, shader);
return GLError.FailedCompilingShader;
} else {
return;
}
}
fn linkProgram(logAlloc: *Allocator, program: c_uint) !void {
c.glLinkProgram(program);
var ok: c_int = undefined;
c.glGetProgramiv(program, c.GL_LINK_STATUS, &ok);
if (ok != c.GL_TRUE) {
std.debug.warn("Unable to link program: {}\n", .{program});
try GL.printShaderLog(logAlloc, program);
return GLError.FailedLinkingProgram;
} else {
return;
}
}
fn printProgramLog(logAlloc: *Allocator, program: c.GLuint) !void {
if (c.glIsProgram(program) != c.GL_TRUE) {
std.debug.warn("Name {} is not a program\n", .{program});
return;
}
var maxLength: c_int = undefined;
c.glGetShaderiv(program, c.GL_INFO_LOG_LENGTH, &maxLength);
if (maxLength == 0) {
std.debug.warn("No shader info log\n", .{});
return;
}
var infoLog: [:0]u8 = try logAlloc.allocWithOptions(
u8,
@intCast(usize, maxLength),
null,
0,
);
defer logAlloc.free(infoLog);
var infoLogLength: c_int = 0;
c.glGetProgramInfoLog(program, maxLength, &infoLogLength, infoLog);
if (infoLogLength > 0) {
std.debug.warn("{s}\n", .{infoLog});
}
}
fn printShaderLog(logAlloc: *Allocator, shader: c.GLuint) !void {
if (c.glIsShader(shader) != c.GL_TRUE) {
std.debug.warn("Name {} is not a shader\n", .{shader});
return;
}
var maxLength: c_int = undefined;
c.glGetShaderiv(shader, c.GL_INFO_LOG_LENGTH, &maxLength);
if (maxLength == 0) {
std.debug.warn("No shader info log\n", .{});
return;
}
var infoLog: [:0]u8 = try logAlloc.allocWithOptions(
u8,
@intCast(usize, maxLength),
null,
0,
);
defer logAlloc.free(infoLog);
var infoLogLength: c_int = 0;
c.glGetShaderInfoLog(shader, maxLength, &infoLogLength, infoLog);
if (infoLogLength > 0) {
std.debug.warn("{s}\n", .{infoLog});
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment