message(FATAL_ERROR "CMAKE_BUILD_TYPE must be Debug or Release.") | message(FATAL_ERROR "CMAKE_BUILD_TYPE must be Debug or Release.") | ||||
endif() | endif() | ||||
# We want to be able to use C++20 designated initializers, | # We want to be able to use C++20 designated initializers, | ||||
# but Clang doesn't support them yet. | # but Clang doesn't support them yet. | ||||
# Remove once Clang 9.1 or something comes out. | # Remove once Clang 9.1 or something comes out. | ||||
add_subdirectory(third-party) | add_subdirectory(third-party) | ||||
add_subdirectory(tracy-tools) | add_subdirectory(tracy-tools) | ||||
add_subdirectory(libcygnet) | |||||
add_subdirectory(libswan) | add_subdirectory(libswan) | ||||
add_subdirectory(core.mod) | add_subdirectory(core.mod) | ||||
add_library(cygnet SHARED | |||||
src/GlProgram.cc | |||||
src/Window.cc) | |||||
target_link_libraries(cygnet PUBLIC SDL2 GLESv2) | |||||
target_include_directories(cygnet | |||||
PUBLIC include | |||||
PRIVATE include/cygnet) | |||||
add_executable(cygnet-hello-triangle | |||||
samples/hello-triangle/hello-triangle.cc) | |||||
target_link_libraries(cygnet-hello-triangle PUBLIC cygnet) |
#pragma once | |||||
#include <vector> | |||||
#include <initializer_list> | |||||
#include <functional> | |||||
#include <SDL_opengles2.h> | |||||
#include "util.h" | |||||
namespace Cygnet { | |||||
class GlShader: NonCopyable { | |||||
public: | |||||
enum class Type { | |||||
VERTEX, | |||||
FRAGMENT, | |||||
}; | |||||
GlShader(const char *source, Type type); | |||||
~GlShader(); | |||||
GLuint id() { return id_; } | |||||
private: | |||||
GLuint id_; | |||||
bool valid_ = false; | |||||
}; | |||||
class GlProgram: NonCopyable { | |||||
public: | |||||
GlProgram(std::initializer_list<std::reference_wrapper<GlShader>> shaders); | |||||
~GlProgram(); | |||||
void use() { glUseProgram(id_); } | |||||
GLuint id() { return id_; } | |||||
GLuint getLocation(const char *name) { return glGetAttribLocation(id_, name); } | |||||
private: | |||||
GLuint id_; | |||||
bool valid_ = false; | |||||
}; | |||||
} |
#pragma once | |||||
#include <SDL.h> | |||||
#include <stdint.h> | |||||
#include "util.h" | |||||
namespace Cygnet { | |||||
class Window { | |||||
public: | |||||
Window(const char *name, int width, int height); | |||||
~Window(); | |||||
void makeCurrent(); | |||||
void clear(); | |||||
void flip(); | |||||
private: | |||||
CPtr<SDL_Window, SDL_DestroyWindow> win_; | |||||
SDL_GLContext glctx_; | |||||
}; | |||||
} |
#pragma once | |||||
#include <memory> | |||||
/* | |||||
* This file mostly just contains copy-pasted stuff from libswan. | |||||
* I don't love the code duplication, but it feels overkill to have a | |||||
* library which both libcygnet and libswan depends on just for this stuff. | |||||
*/ | |||||
namespace Cygnet { | |||||
// Inherit from this class to make a class non-copyable | |||||
class NonCopyable { | |||||
public: | |||||
NonCopyable(const NonCopyable &) = delete; | |||||
NonCopyable(NonCopyable &&) noexcept = default; | |||||
NonCopyable &operator=(const NonCopyable &) = delete; | |||||
NonCopyable &operator=(NonCopyable &&) = default; | |||||
protected: | |||||
NonCopyable() = default; | |||||
~NonCopyable() = default; | |||||
}; | |||||
// Take a deleter function, turn it into a class with an operator() for unique_ptr | |||||
template<typename T, void (*Func)(T *)> | |||||
class CPtrDeleter { | |||||
public: | |||||
void operator()(T *ptr) { Func(ptr); } | |||||
}; | |||||
// This is just a bit nicer to use than using unique_ptr directly | |||||
template<typename T, void (*Func)(T *)> | |||||
using CPtr = std::unique_ptr<T, CPtrDeleter<T, Func>>; | |||||
} |
#include <cygnet/Window.h> | |||||
#include <cygnet/GlProgram.h> | |||||
#include <iostream> | |||||
const char *vertexSource = R"( | |||||
attribute vec4 position; | |||||
void main() { | |||||
gl_Position = vec4(position.xyz, 1.0); | |||||
} | |||||
)"; | |||||
const char *fragmentSource = R"( | |||||
precision mediump float; | |||||
void main() { | |||||
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0); | |||||
} | |||||
)"; | |||||
int main() { | |||||
SDL_Init(SDL_INIT_VIDEO); | |||||
Cygnet::Window win("Hello Triangle", 640, 480); | |||||
Cygnet::GlShader vertex(vertexSource, Cygnet::GlShader::Type::VERTEX); | |||||
Cygnet::GlShader fragment(fragmentSource, Cygnet::GlShader::Type::FRAGMENT); | |||||
Cygnet::GlProgram program({ vertex, fragment }); | |||||
GLuint positionAttrib = program.getLocation("position"); | |||||
program.use(); | |||||
GLfloat vertixes[] = { | |||||
0.0, 0.5, 0.0, | |||||
-0.5, -0.5, 0.0, | |||||
0.5, -0.5, 0.0, | |||||
}; | |||||
// Draw loop | |||||
while (true) { | |||||
SDL_Event evt; | |||||
while (SDL_PollEvent(&evt)) { | |||||
switch (evt.type) { | |||||
case SDL_QUIT: | |||||
goto exit; | |||||
break; | |||||
} | |||||
} | |||||
win.clear(); | |||||
glVertexAttribPointer(positionAttrib, 3, GL_FLOAT, GL_FALSE, 0, vertixes); | |||||
glEnableVertexAttribArray(positionAttrib); | |||||
glDrawArrays(GL_TRIANGLES, 0, 3); | |||||
win.flip(); | |||||
} | |||||
exit: | |||||
SDL_Quit(); | |||||
} |
#include "GlProgram.h" | |||||
#include <iostream> | |||||
#include <stdexcept> | |||||
namespace Cygnet { | |||||
GlShader::GlShader(const char *source, Type type) { | |||||
switch (type) { | |||||
case Type::VERTEX: | |||||
id_ = glCreateShader(GL_VERTEX_SHADER); | |||||
break; | |||||
case Type::FRAGMENT: | |||||
id_ = glCreateShader(GL_FRAGMENT_SHADER); | |||||
break; | |||||
} | |||||
glShaderSource(id_, 1, &source, NULL); | |||||
glCompileShader(id_); | |||||
char log[4096]; | |||||
GLsizei length; | |||||
glGetShaderInfoLog(id_, sizeof(log), &length, log); | |||||
if (length != 0) { | |||||
std::cerr << "Shader compile info:\n" << log << '\n'; | |||||
} | |||||
GLint status; | |||||
glGetShaderiv(id_, GL_COMPILE_STATUS, &status); | |||||
if (status == GL_FALSE) { | |||||
id_ = -1; | |||||
throw std::runtime_error("GL shader compilation failed."); | |||||
} | |||||
valid_ = true; | |||||
} | |||||
GlShader::~GlShader() { | |||||
if (valid_) { | |||||
glDeleteShader(id_); | |||||
} | |||||
} | |||||
GlProgram::GlProgram(std::initializer_list<std::reference_wrapper<GlShader>> shaders) { | |||||
id_ = glCreateProgram(); | |||||
for (GlShader &shader: shaders) { | |||||
glAttachShader(id_, shader.id()); | |||||
} | |||||
glLinkProgram(id_); | |||||
char log[4096]; | |||||
GLsizei length; | |||||
glGetProgramInfoLog(id_, sizeof(log), &length, log); | |||||
if (length != 0) { | |||||
std::cerr << "Program link info:\n" << log << '\n'; | |||||
} | |||||
GLint status; | |||||
glGetProgramiv(id_, GL_LINK_STATUS, &status); | |||||
if (status == GL_FALSE) { | |||||
id_ = -1; | |||||
throw std::runtime_error("GL program link failed."); | |||||
} | |||||
valid_ = true; | |||||
} | |||||
GlProgram::~GlProgram() { | |||||
if (valid_) { | |||||
glDeleteProgram(id_); | |||||
} | |||||
} | |||||
} |
#include "Window.h" | |||||
#include <SDL_opengles2.h> | |||||
#include <assert.h> | |||||
namespace Cygnet { | |||||
Window::Window(const char *name, int width, int height) { | |||||
win_.reset(SDL_CreateWindow( | |||||
name, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, | |||||
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | | |||||
SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_OPENGL)); | |||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); | |||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); | |||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); | |||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); | |||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); | |||||
assert(glctx_ = SDL_GL_CreateContext(win_.get())); | |||||
makeCurrent(); | |||||
assert(SDL_GL_SetSwapInterval(1) == 0); | |||||
} | |||||
Window::~Window() { | |||||
SDL_GL_DeleteContext(glctx_); | |||||
} | |||||
void Window::makeCurrent() { | |||||
SDL_GL_MakeCurrent(win_.get(), glctx_); | |||||
} | |||||
void Window::clear() { | |||||
glClearColor(0, 0, 0, 1); | |||||
glClear(GL_COLOR_BUFFER_BIT); | |||||
} | |||||
void Window::flip() { | |||||
SDL_GL_SwapWindow(win_.get()); | |||||
} | |||||
} |