@@ -52,7 +52,6 @@ else() | |||
message(FATAL_ERROR "CMAKE_BUILD_TYPE must be Debug or Release.") | |||
endif() | |||
# We want to be able to use C++20 designated initializers, | |||
# but Clang doesn't support them yet. | |||
# Remove once Clang 9.1 or something comes out. | |||
@@ -63,6 +62,7 @@ set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib;${CMAKE_INSTALL_PREFIX}/lib | |||
add_subdirectory(third-party) | |||
add_subdirectory(tracy-tools) | |||
add_subdirectory(libcygnet) | |||
add_subdirectory(libswan) | |||
add_subdirectory(core.mod) | |||
@@ -0,0 +1,11 @@ | |||
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) |
@@ -0,0 +1,43 @@ | |||
#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; | |||
}; | |||
} |
@@ -0,0 +1,24 @@ | |||
#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_; | |||
}; | |||
} |
@@ -0,0 +1,37 @@ | |||
#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>>; | |||
} |
@@ -0,0 +1,57 @@ | |||
#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(); | |||
} |
@@ -0,0 +1,76 @@ | |||
#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_); | |||
} | |||
} | |||
} |
@@ -0,0 +1,43 @@ | |||
#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()); | |||
} | |||
} |