/
leanid
/
om
Обзор
Документация
Войти
/
leanid
/
om
Код
Запросы
0
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
01-basic-game-dev/05-2-texture-loading/engine.cxx
614 строк
20 KB
Leanid Chaika
update code to compile with last SDL3
18 фев 2024, 03:21
18 фев 2024, 03:21
9b9084c
Код
Авторство
О чём код?
#include "engine.hxx" #include <algorithm> #include <array> #include <cassert> #include <cstdint> #include <exception> #include <fstream> #include <iostream> #include <sstream> #include <stdexcept> #include <string_view> #include <vector> #include <SDL3/SDL.h> #include <SDL3/SDL_opengl.h> #include <SDL3/SDL_opengl_glext.h> #include "picopng.hxx" // we have to load all extension GL function pointers // dynamically from opengl library // so first declare function pointers for all we need PFNGLCREATESHADERPROC glCreateShader = nullptr; PFNGLSHADERSOURCEPROC glShaderSource = nullptr; PFNGLCOMPILESHADERPROC glCompileShader = nullptr; PFNGLGETSHADERIVPROC glGetShaderiv = nullptr; PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = nullptr; PFNGLDELETESHADERPROC glDeleteShader = nullptr; PFNGLCREATEPROGRAMPROC glCreateProgram = nullptr; PFNGLATTACHSHADERPROC glAttachShader = nullptr; PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation = nullptr; PFNGLLINKPROGRAMPROC glLinkProgram = nullptr; PFNGLGETPROGRAMIVPROC glGetProgramiv = nullptr; PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = nullptr; PFNGLDELETEPROGRAMPROC glDeleteProgram = nullptr; PFNGLUSEPROGRAMPROC glUseProgram = nullptr; PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = nullptr; PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray = nullptr; PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = nullptr; PFNGLUNIFORM1IPROC glUniform1i = nullptr; PFNGLACTIVETEXTUREPROC glActiveTexture_ = nullptr; template <typename T> static void load_gl_func(const char* func_name, T& result) { SDL_FunctionPointer gl_pointer = SDL_GL_GetProcAddress(func_name); if (nullptr == gl_pointer) { throw std::runtime_error(std::string("can't load GL function") + func_name); } result = reinterpret_cast<T>(gl_pointer); } #define OM_GL_CHECK() \ { \ const uint32_t err = glGetError(); \ if (err != GL_NO_ERROR) \ { \ switch (err) \ { \ case GL_INVALID_ENUM: \ std::cerr << "GL_INVALID_ENUM" << std::endl; \ break; \ case GL_INVALID_VALUE: \ std::cerr << "GL_INVALID_VALUE" << std::endl; \ break; \ case GL_INVALID_OPERATION: \ std::cerr << "GL_INVALID_OPERATION" << std::endl; \ break; \ case GL_INVALID_FRAMEBUFFER_OPERATION: \ std::cerr << "GL_INVALID_FRAMEBUFFER_OPERATION" \ << std::endl; \ break; \ case GL_OUT_OF_MEMORY: \ std::cerr << "GL_OUT_OF_MEMORY" << std::endl; \ break; \ } \ assert(false); \ } \ } namespace om { static std::array<std::string_view, 17> event_names = { { /// input events "left_pressed", "left_released", "right_pressed", "right_released", "up_pressed", "up_released", "down_pressed", "down_released", "select_pressed", "select_released", "start_pressed", "start_released", "button1_pressed", "button1_released", "button2_pressed", "button2_released", /// virtual console events "turn_off" } }; std::ostream& operator<<(std::ostream& stream, const event e) { std::uint32_t value = static_cast<std::uint32_t>(e); std::uint32_t minimal = static_cast<std::uint32_t>(event::left_pressed); std::uint32_t maximal = static_cast<std::uint32_t>(event::turn_off); if (value >= minimal && value <= maximal) { stream << event_names[value]; return stream; } else { throw std::runtime_error("too big event value"); } } static std::ostream& operator<<(std::ostream& out, const SDL_Version& v) { out << static_cast<int>(v.major) << '.'; out << static_cast<int>(v.minor) << '.'; out << static_cast<int>(v.patch); return out; } std::istream& operator>>(std::istream& is, vertex& v) { is >> v.x; is >> v.y; is >> v.tx; is >> v.ty; return is; } std::istream& operator>>(std::istream& is, triangle& t) { is >> t.v[0]; is >> t.v[1]; is >> t.v[2]; return is; } struct bind { SDL_Keycode key; std::string_view name; event event_pressed; event event_released; }; const std::array<bind, 8> keys{ { { SDLK_w, "up", event::up_pressed, event::up_released }, { SDLK_a, "left", event::left_pressed, event::left_released }, { SDLK_s, "down", event::down_pressed, event::down_released }, { SDLK_d, "right", event::right_pressed, event::right_released }, { SDLK_LCTRL, "button1", event::button1_pressed, event::button1_released }, { SDLK_SPACE, "button2", event::button2_pressed, event::button2_released }, { SDLK_ESCAPE, "select", event::select_pressed, event::select_released }, { SDLK_RETURN, "start", event::start_pressed, event::start_released } } }; static bool check_input(const SDL_Event& e, const bind*& result) { using namespace std; const auto it = find_if(begin(keys), end(keys), [&](const bind& b) { return b.key == e.key.keysym.sym; }); if (it != end(keys)) { result = &(*it); return true; } return false; } class engine_impl final : public engine { public: /// create main window /// on success return empty string std::string initialize(std::string_view /*config*/) final { using namespace std; stringstream serr; SDL_Version compiled = { 0, 0, 0 }; SDL_Version linked = { 0, 0, 0 }; SDL_VERSION(&compiled) SDL_GetVersion(&linked); if (SDL_COMPILEDVERSION != SDL_VERSIONNUM(linked.major, linked.minor, linked.patch)) { serr << "warning: SDL2 compiled and linked version mismatch: " << compiled << " " << linked << endl; } const int init_result = SDL_Init(SDL_INIT_VIDEO); if (init_result != 0) { const char* err_message = SDL_GetError(); serr << "error: failed call SDL_Init: " << err_message << endl; return serr.str(); } window = SDL_CreateWindow("title", 640, 480, SDL_WINDOW_OPENGL); if (window == nullptr) { const char* err_message = SDL_GetError(); serr << "error: failed call SDL_CreateWindow: " << err_message << endl; SDL_Quit(); return serr.str(); } gl_context = SDL_GL_CreateContext(window); if (gl_context == nullptr) { std::string msg("can't create opengl context: "); msg += SDL_GetError(); serr << msg << endl; return serr.str(); } int gl_major_ver = 0; SDL_GL_GetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, &gl_major_ver); int gl_minor_ver = 0; SDL_GL_GetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, &gl_minor_ver); if (gl_major_ver <= 2 && gl_minor_ver < 1) { serr << "current context opengl version: " << gl_major_ver << '.' << gl_minor_ver << '\n' << "need opengl version at least: 2.1\n" << std::flush; return serr.str(); } try { load_gl_func("glCreateShader", glCreateShader); load_gl_func("glShaderSource", glShaderSource); load_gl_func("glCompileShader", glCompileShader); load_gl_func("glGetShaderiv", glGetShaderiv); load_gl_func("glGetShaderInfoLog", glGetShaderInfoLog); load_gl_func("glDeleteShader", glDeleteShader); load_gl_func("glCreateProgram", glCreateProgram); load_gl_func("glAttachShader", glAttachShader); load_gl_func("glBindAttribLocation", glBindAttribLocation); load_gl_func("glLinkProgram", glLinkProgram); load_gl_func("glGetProgramiv", glGetProgramiv); load_gl_func("glGetProgramInfoLog", glGetProgramInfoLog); load_gl_func("glDeleteProgram", glDeleteProgram); load_gl_func("glUseProgram", glUseProgram); load_gl_func("glVertexAttribPointer", glVertexAttribPointer); load_gl_func("glEnableVertexAttribArray", glEnableVertexAttribArray); load_gl_func("glGetUniformLocation", glGetUniformLocation); load_gl_func("glUniform1i", glUniform1i); load_gl_func("glActiveTexture", glActiveTexture_); } catch (std::exception& ex) { return ex.what(); } // create vertex shader GLuint vert_shader = glCreateShader(GL_VERTEX_SHADER); OM_GL_CHECK() string_view vertex_shader_src = R"( attribute vec2 a_position; attribute vec2 a_tex_coord; varying vec2 v_tex_coord; void main() { v_tex_coord = a_tex_coord; gl_Position = vec4(a_position, 0.0, 1.0); } )"; const char* source = vertex_shader_src.data(); glShaderSource(vert_shader, 1, &source, nullptr); OM_GL_CHECK() glCompileShader(vert_shader); OM_GL_CHECK() GLint compiled_status = 0; glGetShaderiv(vert_shader, GL_COMPILE_STATUS, &compiled_status); OM_GL_CHECK() if (compiled_status == 0) { GLint info_len = 0; glGetShaderiv(vert_shader, GL_INFO_LOG_LENGTH, &info_len); OM_GL_CHECK() std::vector<char> info_chars(static_cast<size_t>(info_len)); glGetShaderInfoLog( vert_shader, info_len, nullptr, info_chars.data()); OM_GL_CHECK() glDeleteShader(vert_shader); OM_GL_CHECK() std::string shader_type_name = "vertex"; serr << "Error compiling shader(vertex)\n" << vertex_shader_src << "\n" << info_chars.data(); return serr.str(); } // create fragment shader GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); OM_GL_CHECK() string_view fragment_shader_src = R"( varying vec2 v_tex_coord; uniform sampler2D s_texture; void main() { gl_FragColor = texture2D(s_texture, v_tex_coord); } )"; source = fragment_shader_src.data(); glShaderSource(fragment_shader, 1, &source, nullptr); OM_GL_CHECK() glCompileShader(fragment_shader); OM_GL_CHECK() compiled_status = 0; glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &compiled_status); OM_GL_CHECK() if (compiled_status == 0) { GLint info_len = 0; glGetShaderiv(fragment_shader, GL_INFO_LOG_LENGTH, &info_len); OM_GL_CHECK() std::vector<char> info_chars(static_cast<size_t>(info_len)); glGetShaderInfoLog( fragment_shader, info_len, nullptr, info_chars.data()); OM_GL_CHECK() glDeleteShader(fragment_shader); OM_GL_CHECK() serr << "Error compiling shader(fragment)\n" << vertex_shader_src << "\n" << info_chars.data(); return serr.str(); } // now create program and attach vertex and fragment shaders GLuint program_id_ = glCreateProgram(); OM_GL_CHECK() if (0 == program_id_) { serr << "failed to create gl program"; return serr.str(); } glAttachShader(program_id_, vert_shader); OM_GL_CHECK() glAttachShader(program_id_, fragment_shader); OM_GL_CHECK() // bind attribute location glBindAttribLocation(program_id_, 0, "a_position"); OM_GL_CHECK() // link program after binding attribute locations glLinkProgram(program_id_); OM_GL_CHECK() // Check the link status GLint linked_status = 0; glGetProgramiv(program_id_, GL_LINK_STATUS, &linked_status); OM_GL_CHECK() if (linked_status == 0) { GLint infoLen = 0; glGetProgramiv(program_id_, GL_INFO_LOG_LENGTH, &infoLen); OM_GL_CHECK() std::vector<char> infoLog(static_cast<size_t>(infoLen)); glGetProgramInfoLog(program_id_, infoLen, nullptr, infoLog.data()); OM_GL_CHECK() serr << "Error linking program:\n" << infoLog.data(); glDeleteProgram(program_id_); OM_GL_CHECK() return serr.str(); } // turn on rendering with just created shader program glUseProgram(program_id_); OM_GL_CHECK() int location = glGetUniformLocation(program_id_, "s_texture"); OM_GL_CHECK() assert(-1 != location); int texture_unit = 0; glActiveTexture_(GL_TEXTURE0 + texture_unit); OM_GL_CHECK() if (!load_texture("./01-basic-game-dev/05-2-texture-loading/tank.png")) { return "failed load texture\n"; } // http://www.khronos.org/opengles/sdk/docs/man/xhtml/glUniform.xml glUniform1i(location, 0 + texture_unit); OM_GL_CHECK() glEnable(GL_BLEND); OM_GL_CHECK() glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); OM_GL_CHECK() return ""; } /// return seconds from initialization float get_time_from_init() final { std::uint64_t ms_from_library_initialization = SDL_GetTicks(); float seconds = ms_from_library_initialization * 0.001f; return seconds; } /// pool event from input queue /// return true if more events in queue bool read_input(event& e) final { using namespace std; // collect all events from SDL SDL_Event sdl_event; if (SDL_PollEvent(&sdl_event)) { const bind* binding = nullptr; if (sdl_event.type == SDL_EVENT_QUIT) { e = event::turn_off; return true; } else if (sdl_event.type == SDL_EVENT_KEY_DOWN) { if (check_input(sdl_event, binding)) { e = binding->event_pressed; return true; } } else if (sdl_event.type == SDL_EVENT_KEY_UP) { if (check_input(sdl_event, binding)) { e = binding->event_released; return true; } } } return false; } bool load_texture(std::string_view path) final { std::vector<std::byte> png_file_in_memory; std::ifstream ifs(path.data(), std::ios_base::binary); if (!ifs) { return false; } ifs.seekg(0, std::ios_base::end); size_t pos_in_file = static_cast<size_t>(ifs.tellg()); png_file_in_memory.resize(pos_in_file); ifs.seekg(0, std::ios_base::beg); if (!ifs) { return false; } ifs.read(reinterpret_cast<char*>(png_file_in_memory.data()), static_cast<std::streamsize>(png_file_in_memory.size())); if (!ifs.good()) { return false; } std::vector<std::byte> image; unsigned long w = 0; unsigned long h = 0; int error = decodePNG(image, w, h, png_file_in_memory.data(), png_file_in_memory.size(), false); // if there's an error, display it if (error != 0) { std::cerr << "error: " << error << std::endl; return false; } GLuint tex_handl = 0; glGenTextures(1, &tex_handl); OM_GL_CHECK() glBindTexture(GL_TEXTURE_2D, tex_handl); OM_GL_CHECK() GLint mipmap_level = 0; GLint border = 0; // clang-format off glTexImage2D(GL_TEXTURE_2D, // Specifies the target texture of the active texture unit mipmap_level, // Specifies the level-of-detail number. Level 0 is the base image level GL_RGBA, // Specifies the internal format of the texture static_cast<GLsizei>(w), static_cast<GLsizei>(h), border, // Specifies the width of the border. Must be 0. For GLES 2.0 GL_RGBA, // Specifies the format of the texel data. Must match internalformat GL_UNSIGNED_BYTE, // Specifies the data type of the texel data &image[0]); // Specifies a pointer to the image data in memory // clang-format on OM_GL_CHECK() glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); OM_GL_CHECK() glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); OM_GL_CHECK() return true; } void render_triangle(const triangle& t) final { // vertex coordinates glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, sizeof(vertex), &t.v[0].x); OM_GL_CHECK() glEnableVertexAttribArray(0); OM_GL_CHECK() // texture coordinates glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, sizeof(vertex), &t.v[0].tx); OM_GL_CHECK() glEnableVertexAttribArray(1); OM_GL_CHECK() glDrawArrays(GL_TRIANGLES, 0, 3); OM_GL_CHECK() } void swap_buffers() final { SDL_GL_SwapWindow(window); glClearColor(0.f, 1.0, 0.f, 0.0f); OM_GL_CHECK() glClear(GL_COLOR_BUFFER_BIT); OM_GL_CHECK() } void uninitialize() final { SDL_GL_DeleteContext(gl_context); SDL_DestroyWindow(window); SDL_Quit(); } private: SDL_Window* window = nullptr; SDL_GLContext gl_context = nullptr; }; static bool already_exist = false; engine* create_engine() { if (already_exist) { throw std::runtime_error("engine already exist"); } engine* result = new engine_impl(); already_exist = true; return result; } void destroy_engine(engine* e) { if (already_exist == false) { throw std::runtime_error("engine not created"); } if (nullptr == e) { throw std::runtime_error("e is nullptr"); } delete e; } engine::~engine() {} } // end namespace om