diff --git a/Makefile b/Makefile index f6ac57c..73a9a42 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,26 @@ CFLAGS = -Wall -Wextra -I./include/ -g LIBS = $(shell pkg-config --libs wayland-client wayland-egl egl glesv2) -build/main: src/main.cpp include/window.hpp build/shader.o build/xdg-shell-protocol.o - g++ -o build/main $(CFLAGS) src/main.cpp build/shader.o build/xdg-shell-protocol.o $(LIBS) +build/main: src/main.cpp build/shader.o build/state.o build/renderer.o build/window.o build/window_wayland.o build/xdg-shell-protocol.o + g++ -o build/main $(CFLAGS) src/main.cpp build/shader.o build/state.o build/renderer.o build/window.o build/window_wayland.o build/xdg-shell-protocol.o $(LIBS) build/shader.o: src/shader.cpp include/shader.h g++ -o build/shader.o $(CFLAGS) -c src/shader.cpp +# TODO: dynamic state impl selection depending on platform (wayland/x11) +build/state.o: src/state/wayland.cpp include/state/wayland.h + g++ -o build/state.o $(CFLAGS) -c src/state/wayland.cpp + +build/renderer.o: src/renderer.cpp include/renderer.h + g++ -o build/renderer.o $(CFLAGS) -c src/renderer.cpp + +build/window.o: src/window.cpp include/window.h + g++ -o build/window.o $(CFLAGS) -c src/window.cpp + +# TODO: dynamic window impl selection depending on platform (wayland/x11) +build/window_wayland.o: include/window/wayland.h src/window/wayland.cpp + g++ -o build/window_wayland.o $(CFLAGS) -c src/window/wayland.cpp + build/xdg-shell-protocol.o: src/xdg-shell-protocol.c gcc -o build/xdg-shell-protocol.o -c $(CFLAGS) $(LIBS) src/xdg-shell-protocol.c diff --git a/include/renderer.h b/include/renderer.h new file mode 100644 index 0000000..6baf87f --- /dev/null +++ b/include/renderer.h @@ -0,0 +1,40 @@ +#pragma once +#ifndef H_RENDERER_ +#define H_RENDERER_ + +#include +#include +#include + +class Renderer { +public: + static Renderer *GetInstance(); + +public: + const EGLConfig &GetConfig() const; + +public: + void DestroyWindow(const EGLSurface& surface, struct wl_egl_window* window); + + void DestroyContext(const EGLContext& context); + + EGLContext CreateContext(); + + EGLSurface CreateSurface(struct wl_egl_window *window); + + void SurfaceSetContext(const EGLSurface &surface, const EGLContext &context); + + void SwapBuffers(const EGLSurface &surface); + +private: + Renderer(); + ~Renderer(); + +private: + static Renderer *s_instance; + + EGLDisplay m_egl_display; + EGLConfig m_config; +}; + +#endif // H_RENDERER_ diff --git a/include/state/wayland.h b/include/state/wayland.h new file mode 100644 index 0000000..a229063 --- /dev/null +++ b/include/state/wayland.h @@ -0,0 +1,40 @@ +#pragma once +#ifndef H_WAYLAND_STATE_ +#define H_WAYLAND_STATE_ + +#include + +#include "xdg-shell-client-protocol.h" + +class WaylandState { +public: + static WaylandState *GetInstance(); + +private: + static void wm_base_handle_ping(void *data, struct xdg_wm_base *wm_base, + uint32_t serial); + static void registry_handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, + uint32_t version); + static void registry_handle_global_remove(void *data, + struct wl_registry *registry, + uint32_t name); +private: + WaylandState(); + ~WaylandState(); + +private: + static WaylandState *s_instance; + + struct wl_display *m_display = nullptr; + struct wl_compositor *m_compositor = nullptr; + struct xdg_wm_base *m_wm_base = nullptr; + + struct xdg_wm_base_listener m_wm_base_listener; + struct wl_registry_listener m_reg_listener; + + friend class WaylandWindowImpl; + friend class Renderer; +}; + +#endif // H_WAYLAND_STATE_ diff --git a/include/window.h b/include/window.h new file mode 100644 index 0000000..0cefaa9 --- /dev/null +++ b/include/window.h @@ -0,0 +1,52 @@ +#pragma once +#ifndef H_WINDOW_ +#define H_WINDOW_ + +#include + +#include +#include +#include + +#define UNUSED(x) (void)(x) + +class WindowImpl { +public: + WindowImpl() = default; + virtual ~WindowImpl() = default; + + WindowImpl(const WindowImpl&) = delete; + WindowImpl(WindowImpl&&) = delete; +protected: + virtual bool Dispatch() = 0; + + virtual size_t GetWidth() const = 0; + virtual size_t GetHeight() const = 0; + + friend class Window; +}; + +class Window { +public: + Window(size_t width, size_t height); + virtual ~Window(); + + Window(const Window &) = delete; + Window(Window &&) = delete; + +public: + bool Running() const; + size_t GetWidth() const; + size_t GetHeight() const; + +private: + WindowImpl* m_impl; + +private: // app logic + uint32_t m_last_time; + size_t m_width; + size_t m_height; + bool m_running; +}; + +#endif // H_WINDOW_ diff --git a/include/window.hpp b/include/window.hpp deleted file mode 100644 index 9c6e468..0000000 --- a/include/window.hpp +++ /dev/null @@ -1,465 +0,0 @@ -#ifndef H_WINDOW_ -#define H_WINDOW_ - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "shader.h" -#include "xdg-shell-client-protocol.h" - -#define UNUSED(x) (void)(x) - -class Window; -class Renderer; - -class AppState { -public: - static AppState *GetInstance() { - if (!AppState::s_instance) - AppState::s_instance = new AppState(); - return AppState::s_instance; - } - -private: - static void wm_base_handle_ping(void *data, struct xdg_wm_base *wm_base, - uint32_t serial) { - UNUSED(data); - printf("INFO: ping method fired! sending pong...\n"); - xdg_wm_base_pong(wm_base, serial); - } - - static void registry_handle_global(void *data, struct wl_registry *registry, - uint32_t name, const char *interface, - uint32_t version) { - UNUSED(data); - auto state = reinterpret_cast(data); - - // printf("HANDLE_GLOBAL: new interface '%s', name = %d, version = %d\n", - // interface, name, version); - std::string interface_name(interface); - if (interface_name == "wl_compositor") { - state->m_compositor = reinterpret_cast( - wl_registry_bind(registry, name, &wl_compositor_interface, version)); - printf("[DEBUG] global_binding(%s): compositor %p\n", interface, - state->m_compositor); - } - if (interface_name == "xdg_wm_base") { - state->m_wm_base = reinterpret_cast( - wl_registry_bind(registry, name, &xdg_wm_base_interface, version)); - printf("[DEBUG] global_binding(%s): xdg_wm_base %p\n", interface, - state->m_wm_base); - if (state->m_wm_base == NULL) { - fprintf(stderr, "ERROR: could not bind to xdg_wm_base\n"); - } - state->m_wm_base_listener.ping = wm_base_handle_ping; - xdg_wm_base_add_listener(state->m_wm_base, &state->m_wm_base_listener, - NULL); - } - } - - static void registry_handle_global_remove(void *data, - struct wl_registry *registry, - uint32_t name) { - UNUSED(data); - UNUSED(registry); - UNUSED(name); - } - -private: - AppState() { - m_display = wl_display_connect(NULL); - if (m_display == NULL) { - fprintf(stderr, "ERROR: could not connect to wl display\n"); - exit(1); - } - - struct wl_registry *registry = wl_display_get_registry(m_display); - if (registry == NULL) { - fprintf(stderr, "ERROR: could not connect to wl registry\n"); - exit(1); - } - - m_reg_listener.global = registry_handle_global; - m_reg_listener.global_remove = registry_handle_global_remove; - - auto handle = wl_registry_add_listener(registry, &m_reg_listener, this); - printf("[DEBUG] wl_registry listener bound: %d\n", handle); - - wl_display_roundtrip(m_display); - } - ~AppState() { - wl_display_flush(m_display); - wl_display_roundtrip(m_display); - - if (m_wm_base) - xdg_wm_base_destroy(m_wm_base); - if (m_compositor) - wl_compositor_destroy(m_compositor); - - wl_display_disconnect(m_display); - } - -private: - static AppState *s_instance; - - struct wl_display *m_display = nullptr; - struct wl_compositor *m_compositor = nullptr; - struct xdg_wm_base *m_wm_base = nullptr; - - struct xdg_wm_base_listener m_wm_base_listener; - struct wl_registry_listener m_reg_listener; - - friend class Window; - friend class Renderer; -}; - -AppState *AppState::s_instance = nullptr; - -class Renderer { -public: - static Renderer *GetInstance() { - if (!s_instance) - s_instance = new Renderer; - return s_instance; - } - -public: - const EGLConfig &GetConfig() const { return m_config; } - -public: - void DestroyWindow(const EGLSurface& surface, struct wl_egl_window* window) { - eglMakeCurrent(m_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - eglDestroySurface(m_egl_display, surface); - wl_egl_window_destroy(window); - } - - void DestroyContext(const EGLContext& context) { - eglDestroyContext(m_egl_display, context); - } - - EGLContext CreateContext() { - EGLint ctx_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; - - return eglCreateContext(m_egl_display, m_config, EGL_NO_CONTEXT, - ctx_attribs); - } - - EGLSurface CreateSurface(struct wl_egl_window *window) { - PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC createPlatformWindowSurface = - reinterpret_cast( - eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT")); - - return createPlatformWindowSurface(m_egl_display, m_config, window, NULL); - } - - void SurfaceSetContext(const EGLSurface &surface, const EGLContext &context) { - if (!eglMakeCurrent(m_egl_display, surface, surface, context)) { - fprintf(stderr, "ERROR: could not change current EGL context\n"); - fprintf(stderr, "EGL error: 0x%x\n", eglGetError()); - exit(1); - } - } - - void SwapBuffers(const EGLSurface &surface) { - eglSwapBuffers(m_egl_display, surface); - } - -private: - Renderer() { - PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplay = - reinterpret_cast( - eglGetProcAddress("eglGetPlatformDisplayEXT")); - - m_egl_display = getPlatformDisplay( - EGL_PLATFORM_WAYLAND_KHR, AppState::GetInstance()->m_display, NULL); - if (m_egl_display == NULL) { - fprintf(stderr, "ERROR: could not get platform display\n"); - fprintf(stderr, "EGL error: 0x%x\n", eglGetError()); - exit(1); - } - - if (!eglInitialize(m_egl_display, NULL, NULL)) { - fprintf(stderr, "ERROR: could not initialize EGL\n"); - fprintf(stderr, "EGL error: 0x%x\n", eglGetError()); - exit(1); - } - - EGLint config_attribs[] = {EGL_SURFACE_TYPE, - EGL_WINDOW_BIT, - EGL_RENDERABLE_TYPE, - EGL_OPENGL_ES2_BIT, - EGL_RED_SIZE, - 8, - EGL_GREEN_SIZE, - 8, - EGL_BLUE_SIZE, - 8, - EGL_ALPHA_SIZE, - 8, - EGL_NONE}; - - EGLint num_configs; - if (!eglChooseConfig(m_egl_display, config_attribs, &m_config, 1, - &num_configs)) { - fprintf(stderr, "ERROR: could not choose EGL config\n"); - fprintf(stderr, "EGL error: 0x%x\n", eglGetError()); - exit(1); - } - } - ~Renderer() { - // After all windows are destroyed: - eglTerminate(m_egl_display); - } - -private: - static Renderer *s_instance; - - EGLDisplay m_egl_display; - EGLConfig m_config; -}; - -Renderer *Renderer::s_instance = nullptr; - -class Window { -private: - static void handle_frame_callback(void *data, struct wl_callback *cb, - uint32_t time) { - auto window = reinterpret_cast(data); - if (!window->m_runnning) return; - // auto delta_time = time - window->m_last_time; - // printf("INFO: frame callback called\n"); - // printf("INFO: time: {%u}\n", time); - // printf("INFO: last_time: {%u}\n", window->m_last_time); - // printf("INFO: delta_time: {%u}\n", delta_time); - // printf("INFO: fps: {%u}\n", 1000 / delta_time); - window->m_last_time = time; - - wl_callback_destroy(cb); - auto frame_callback = wl_surface_frame(window->m_wsurface); - - window->m_frame_listener.done = handle_frame_callback; - - wl_callback_add_listener(frame_callback, &window->m_frame_listener, window); - - GLint angle_loc = glGetUniformLocation(window->m_program, "angle"); - - static float angle = 0.0f; - angle += 0.02f; - - glClear(GL_COLOR_BUFFER_BIT); - - glUniform1f(angle_loc, angle); - glDrawArrays(GL_TRIANGLES, 0, 3); - - Renderer::GetInstance()->SwapBuffers(window->m_egl_surface); - } - - static void xsurface_handle_configure(void *data, struct xdg_surface *surface, - uint32_t serial) { - auto window = reinterpret_cast(data); - - xdg_surface_ack_configure(surface, serial); - - auto frame_callback = wl_surface_frame(window->m_wsurface); - printf("frame cb: %p\n", frame_callback); - - window->m_frame_listener.done = handle_frame_callback; - - int cb_res = wl_callback_add_listener(frame_callback, - &window->m_frame_listener, window); - - printf("mount callback listener: %d\n", cb_res); - - glClear(GL_COLOR_BUFFER_BIT); - Renderer::GetInstance()->SwapBuffers(window->m_egl_surface); - } - - static void xtoplevel_handle_configure(void *data, - struct xdg_toplevel *toplevel, - int32_t width, int32_t height, - struct wl_array *states) { - UNUSED(toplevel); - auto window = reinterpret_cast(data); - printf("[DEBUG] received toplevel configure event\n"); - printf("[DEBUG] width = %d, height = %d\n", width, height); - printf("[DEBUG] %zu states\n", states->size); - - wl_egl_window_resize(window->m_egl_window, width, height, 0, 0); - glViewport(0, 0, width, height); - xdg_surface_set_window_geometry(window->m_xsurface, 0, 0, width, height); - window->m_width = width; - window->m_height = height; - } - - static void xtoplevel_handle_close(void *data, - struct xdg_toplevel *toplevel) { - auto window = reinterpret_cast(data); - UNUSED(toplevel); - printf("[DEBUG] window close request received\n"); - window->m_runnning = false; - - glDeleteProgram(window->m_program); - Renderer::GetInstance()->DestroyWindow(window->m_egl_surface, window->m_egl_window); - xdg_toplevel_destroy(window->m_toplevel); - xdg_surface_destroy(window->m_xsurface); - wl_surface_destroy(window->m_wsurface); - // if (window->m_program) { - // eglMakeCurrent(renderer_display, m_egl_surface, m_egl_surface, m_egl_context); - // glDeleteProgram(m_program); - // glDeleteBuffers(1, &m_vbo); // if used - // eglMakeCurrent(renderer_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - // m_program = 0; - // } - Renderer::GetInstance()->DestroyContext(window->m_egl_context); - } - - static void xtoplevel_handle_configure_bounds(void *data, - struct xdg_toplevel *toplevel, - int32_t width, int32_t height) { - UNUSED(data); - UNUSED(toplevel); - printf("[DEBUG] window configure bounds, width = %d, height = %d\n", width, - height); - } - - static void xtoplevel_handle_wm_capabilities(void *data, - struct xdg_toplevel *toplevel, - struct wl_array *capabilities) { - UNUSED(data); - UNUSED(toplevel); - UNUSED(capabilities); - printf("[DEBUG] window wm_capabilities event received\n"); - } - -public: - Window(size_t width, size_t height) : m_frame_listener({0}), m_last_time(0), m_width(width), m_height(height) { - m_wsurface = - wl_compositor_create_surface(AppState::GetInstance()->m_compositor); - if (m_wsurface == NULL) { - fprintf(stderr, "ERROR: could not connect create a surface\n"); - exit(1); - } - - m_egl_window = wl_egl_window_create(m_wsurface, width, height); - if (m_egl_window == NULL) { - fprintf(stderr, "ERROR: could not create wl_egl_window\n"); - exit(1); - } - - m_egl_surface = Renderer::GetInstance()->CreateSurface(m_egl_window); - if (m_egl_surface == NULL) { - fprintf(stderr, "ERROR: could not create EGL window surface\n"); - fprintf(stderr, "EGL error: 0x%x\n", eglGetError()); - exit(1); - } - - m_egl_context = Renderer::GetInstance()->CreateContext(); - if (m_egl_context == NULL) { - fprintf(stderr, "ERROR: could not create EGL context\n"); - fprintf(stderr, "EGL error: 0x%x\n", eglGetError()); - exit(1); - } - - Renderer::GetInstance()->SurfaceSetContext(m_egl_surface, m_egl_context); - - const char *vs_src = "attribute vec2 pos;\n" - "uniform float angle;\n" - "varying vec2 vPos;\n" - "void main() {\n" - " float c = cos(angle);\n" - " float s = sin(angle);\n" - " mat2 rot = mat2(c, -s, s, c);\n" - " gl_Position = vec4(rot * pos, 0.0, 1.0);\n" - " vPos = rot * pos;\n" - "}\n"; - - const char *fs_src = "precision mediump float;\n" - "varying vec2 vPos;\n" - "void main() {\n" - " gl_FragColor = vec4(vPos, 0.2, 1.0);\n" - "}\n"; - - m_program = create_program(vs_src, fs_src); - glUseProgram(m_program); - - GLint pos_loc = glGetAttribLocation(m_program, "pos"); - // GLint angle_loc = glGetUniformLocation(app_state->program, "angle"); - - float *vertices = new float[6]{0.0f, 0.6f, -0.6f, -0.6f, 0.6f, -0.6f}; - - glEnableVertexAttribArray(pos_loc); - glVertexAttribPointer(pos_loc, 2, GL_FLOAT, GL_FALSE, 0, vertices); - glViewport(0, 0, width, height); - glClearColor(0.2f, 0.4f, 0.8f, 1.0f); - - m_xsurface = xdg_wm_base_get_xdg_surface(AppState::GetInstance()->m_wm_base, - m_wsurface); - if (m_xsurface == NULL) { - fprintf(stderr, "ERROR: could not get an xdg surface\n"); - exit(1); - } - - m_toplevel = xdg_surface_get_toplevel(m_xsurface); - if (m_toplevel == NULL) { - fprintf(stderr, "ERROR: could not get an xdg toplevel\n"); - exit(1); - } - - m_xsurface_listener.configure = xsurface_handle_configure; - xdg_surface_add_listener(m_xsurface, &m_xsurface_listener, this); - - m_toplevel_listener.configure = xtoplevel_handle_configure; - m_toplevel_listener.configure_bounds = xtoplevel_handle_configure_bounds; - m_toplevel_listener.close = xtoplevel_handle_close; - m_toplevel_listener.wm_capabilities = xtoplevel_handle_wm_capabilities; - xdg_toplevel_add_listener(m_toplevel, &m_toplevel_listener, this); - - wl_surface_commit(m_wsurface); - - m_runnning = true; - } - ~Window() {} - - Window(const Window &) = delete; - Window(Window &&) = delete; - -public: - bool Running() { - return m_runnning && wl_display_dispatch(AppState::GetInstance()->m_display) != -1; - } - -private: - EGLSurface m_egl_surface; - EGLContext m_egl_context; - - GLuint m_program; - - struct wl_egl_window *m_egl_window; - struct wl_surface *m_wsurface; - struct xdg_surface *m_xsurface; - struct xdg_toplevel *m_toplevel; - - struct wl_callback_listener m_frame_listener; - struct xdg_surface_listener m_xsurface_listener; - struct xdg_toplevel_listener m_toplevel_listener; - -private: // app logic - uint32_t m_last_time; - size_t m_width; - size_t m_height; - bool m_runnning; -}; - -#endif // H_WINDOW_ diff --git a/include/window/wayland.h b/include/window/wayland.h new file mode 100644 index 0000000..8b5e207 --- /dev/null +++ b/include/window/wayland.h @@ -0,0 +1,49 @@ +#include "window.h" + +#include +#include "xdg-shell-client-protocol.h" + +class WaylandWindowImpl : public WindowImpl { +public: + WaylandWindowImpl(size_t width, size_t height); + ~WaylandWindowImpl() override; + +public: + bool Dispatch() override; + + size_t GetWidth() const override; + size_t GetHeight() const override; + +private: + static void handle_frame_callback(void *data, struct wl_callback *cb, + uint32_t time); + static void xsurface_handle_configure(void *data, struct xdg_surface *surface, + uint32_t serial); + static void xtoplevel_handle_configure(void *data, + struct xdg_toplevel *toplevel, + int32_t width, int32_t height, + struct wl_array *states); + static void xtoplevel_handle_close(void *data, struct xdg_toplevel *toplevel); + static void xtoplevel_handle_configure_bounds(void *data, + struct xdg_toplevel *toplevel, + int32_t width, int32_t height); + static void xtoplevel_handle_wm_capabilities(void *data, + struct xdg_toplevel *toplevel, + struct wl_array *capabilities); + +private: + EGLSurface m_egl_surface; + EGLContext m_egl_context; + GLuint m_program; + struct wl_egl_window *m_egl_window; + struct wl_surface *m_wsurface; + struct xdg_surface *m_xsurface; + struct xdg_toplevel *m_toplevel; + struct wl_callback_listener m_frame_listener; + struct xdg_surface_listener m_xsurface_listener; + struct xdg_toplevel_listener m_toplevel_listener; + + bool m_running = false; + size_t m_width, m_height; +}; + diff --git a/src/main.cpp b/src/main.cpp index fb74f08..e2220ac 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,7 @@ -#include "window.hpp" +#include + +#include "window.h" +#include "renderer.h" #define UNUSED(x) (void)(x) @@ -6,13 +9,13 @@ int main(int argc, char **argv) { UNUSED(argc); UNUSED(argv); - AppState::GetInstance(); Renderer::GetInstance(); Window window(720, 480); + printf("[APP]: starting window"); while (window.Running()) { - + printf("[APP]: width = %zu, height = %zu\n", window.GetWidth(), window.GetHeight()); } return 0; diff --git a/src/renderer.cpp b/src/renderer.cpp new file mode 100644 index 0000000..3eb4086 --- /dev/null +++ b/src/renderer.cpp @@ -0,0 +1,98 @@ +#include + +#include "state/wayland.h" +#include "renderer.h" + +Renderer *Renderer::GetInstance() { + if (!s_instance) + s_instance = new Renderer; + return s_instance; +} + +const EGLConfig &Renderer::GetConfig() const { return m_config; } + +void Renderer::DestroyWindow(const EGLSurface& surface, struct wl_egl_window* window) { + eglMakeCurrent(m_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + eglDestroySurface(m_egl_display, surface); + wl_egl_window_destroy(window); +} + +void Renderer::DestroyContext(const EGLContext& context) { + eglDestroyContext(m_egl_display, context); +} + +EGLContext Renderer::CreateContext() { + EGLint ctx_attribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; + + return eglCreateContext(m_egl_display, m_config, EGL_NO_CONTEXT, + ctx_attribs); +} + +EGLSurface Renderer::CreateSurface(struct wl_egl_window *window) { + PFNEGLCREATEPLATFORMWINDOWSURFACEEXTPROC createPlatformWindowSurface = + reinterpret_cast( + eglGetProcAddress("eglCreatePlatformWindowSurfaceEXT")); + + return createPlatformWindowSurface(m_egl_display, m_config, window, NULL); +} + +void Renderer::SurfaceSetContext(const EGLSurface &surface, const EGLContext &context) { + if (!eglMakeCurrent(m_egl_display, surface, surface, context)) { + fprintf(stderr, "ERROR: could not change current EGL context\n"); + fprintf(stderr, "EGL error: 0x%x\n", eglGetError()); + exit(1); + } +} + +void Renderer::SwapBuffers(const EGLSurface &surface) { + eglSwapBuffers(m_egl_display, surface); +} + +Renderer::Renderer() { + PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplay = + reinterpret_cast( + eglGetProcAddress("eglGetPlatformDisplayEXT")); + + m_egl_display = getPlatformDisplay( + EGL_PLATFORM_WAYLAND_KHR, WaylandState::GetInstance()->m_display, NULL); + if (m_egl_display == NULL) { + fprintf(stderr, "ERROR: could not get platform display\n"); + fprintf(stderr, "EGL error: 0x%x\n", eglGetError()); + exit(1); + } + + if (!eglInitialize(m_egl_display, NULL, NULL)) { + fprintf(stderr, "ERROR: could not initialize EGL\n"); + fprintf(stderr, "EGL error: 0x%x\n", eglGetError()); + exit(1); + } + + EGLint config_attribs[] = {EGL_SURFACE_TYPE, + EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES2_BIT, + EGL_RED_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_ALPHA_SIZE, + 8, + EGL_NONE}; + + EGLint num_configs; + if (!eglChooseConfig(m_egl_display, config_attribs, &m_config, 1, + &num_configs)) { + fprintf(stderr, "ERROR: could not choose EGL config\n"); + fprintf(stderr, "EGL error: 0x%x\n", eglGetError()); + exit(1); + } +} + +Renderer::~Renderer() { + // After all windows are destroyed: + eglTerminate(m_egl_display); +} + +Renderer *Renderer::s_instance = nullptr; diff --git a/src/state/wayland.cpp b/src/state/wayland.cpp new file mode 100644 index 0000000..cb49566 --- /dev/null +++ b/src/state/wayland.cpp @@ -0,0 +1,96 @@ +#include "state/wayland.h" + +#include +#include +#include + +#include "xdg-shell-client-protocol.h" + +#define UNUSED(x) (void)(x) + +WaylandState *WaylandState::GetInstance() { + if (!WaylandState::s_instance) + WaylandState::s_instance = new WaylandState(); + return WaylandState::s_instance; +} + +void WaylandState::wm_base_handle_ping(void *data, struct xdg_wm_base *wm_base, + uint32_t serial) { + UNUSED(data); + printf("INFO: ping method fired! sending pong...\n"); + xdg_wm_base_pong(wm_base, serial); +} + +void WaylandState::registry_handle_global(void *data, struct wl_registry *registry, + uint32_t name, const char *interface, + uint32_t version) { + UNUSED(data); + auto state = reinterpret_cast(data); + + // printf("HANDLE_GLOBAL: new interface '%s', name = %d, version = %d\n", + // interface, name, version); + std::string interface_name(interface); + if (interface_name == "wl_compositor") { + state->m_compositor = reinterpret_cast( + wl_registry_bind(registry, name, &wl_compositor_interface, version)); + printf("[DEBUG] global_binding(%s): compositor %p\n", interface, + state->m_compositor); + } + if (interface_name == "xdg_wm_base") { + state->m_wm_base = reinterpret_cast( + wl_registry_bind(registry, name, &xdg_wm_base_interface, version)); + printf("[DEBUG] global_binding(%s): xdg_wm_base %p\n", interface, + state->m_wm_base); + if (state->m_wm_base == NULL) { + fprintf(stderr, "ERROR: could not bind to xdg_wm_base\n"); + } + state->m_wm_base_listener.ping = wm_base_handle_ping; + xdg_wm_base_add_listener(state->m_wm_base, &state->m_wm_base_listener, + NULL); + } +} + +void WaylandState::registry_handle_global_remove(void *data, + struct wl_registry *registry, + uint32_t name) { + UNUSED(data); + UNUSED(registry); + UNUSED(name); +} + +WaylandState::WaylandState() { + m_display = wl_display_connect(NULL); + if (m_display == NULL) { + fprintf(stderr, "ERROR: could not connect to wl display\n"); + exit(1); + } + + struct wl_registry *registry = wl_display_get_registry(m_display); + if (registry == NULL) { + fprintf(stderr, "ERROR: could not connect to wl registry\n"); + exit(1); + } + + m_reg_listener.global = registry_handle_global; + m_reg_listener.global_remove = registry_handle_global_remove; + + auto handle = wl_registry_add_listener(registry, &m_reg_listener, this); + printf("[DEBUG] wl_registry listener bound: %d\n", handle); + + wl_display_roundtrip(m_display); +} + +WaylandState::~WaylandState() { + wl_display_flush(m_display); + wl_display_roundtrip(m_display); + + if (m_wm_base) + xdg_wm_base_destroy(m_wm_base); + if (m_compositor) + wl_compositor_destroy(m_compositor); + + wl_display_disconnect(m_display); +} + +WaylandState *WaylandState::s_instance = nullptr; + diff --git a/src/window.cpp b/src/window.cpp new file mode 100644 index 0000000..615ac1c --- /dev/null +++ b/src/window.cpp @@ -0,0 +1,24 @@ +#include + +#include "window.h" + +#define WINDOW_WAYLAND 1 + +#ifdef WINDOW_WAYLAND +#include "window/wayland.h" +#endif + +Window::Window(size_t width, size_t height) + : m_last_time(0), m_width(width), m_height(height) { +#ifdef WINDOW_WAYLAND + m_impl = new WaylandWindowImpl(width, height); +#endif + } + +Window::~Window() {} + +size_t Window::GetWidth() const { return m_impl->GetWidth(); } +size_t Window::GetHeight() const { return m_impl->GetHeight(); } + +bool Window::Running() const { return m_running && m_impl->Dispatch(); } + diff --git a/src/window/wayland.cpp b/src/window/wayland.cpp new file mode 100644 index 0000000..4b73b81 --- /dev/null +++ b/src/window/wayland.cpp @@ -0,0 +1,214 @@ +#include + +#include "state/wayland.h" +#include "window/wayland.h" + +#include "renderer.h" +#include "shader.h" + +WaylandWindowImpl::WaylandWindowImpl(size_t width, size_t height) + : m_frame_listener({0}), m_width(width), m_height(height) { + m_wsurface = + wl_compositor_create_surface(WaylandState::GetInstance()->m_compositor); + if (m_wsurface == NULL) { + fprintf(stderr, "ERROR: could not connect create a surface\n"); + exit(1); + } + + m_egl_window = wl_egl_window_create(m_wsurface, width, height); + if (m_egl_window == NULL) { + fprintf(stderr, "ERROR: could not create wl_egl_window\n"); + exit(1); + } + + m_egl_surface = Renderer::GetInstance()->CreateSurface(m_egl_window); + if (m_egl_surface == NULL) { + fprintf(stderr, "ERROR: could not create EGL window surface\n"); + fprintf(stderr, "EGL error: 0x%x\n", eglGetError()); + exit(1); + } + + m_egl_context = Renderer::GetInstance()->CreateContext(); + if (m_egl_context == NULL) { + fprintf(stderr, "ERROR: could not create EGL context\n"); + fprintf(stderr, "EGL error: 0x%x\n", eglGetError()); + exit(1); + } + + Renderer::GetInstance()->SurfaceSetContext(m_egl_surface, m_egl_context); + + const char *vs_src = "attribute vec2 pos;\n" + "uniform float angle;\n" + "varying vec2 vPos;\n" + "void main() {\n" + " float c = cos(angle);\n" + " float s = sin(angle);\n" + " mat2 rot = mat2(c, -s, s, c);\n" + " gl_Position = vec4(rot * pos, 0.0, 1.0);\n" + " vPos = rot * pos;\n" + "}\n"; + + const char *fs_src = "precision mediump float;\n" + "varying vec2 vPos;\n" + "void main() {\n" + " gl_FragColor = vec4(vPos, 0.2, 1.0);\n" + "}\n"; + + m_program = create_program(vs_src, fs_src); + glUseProgram(m_program); + + GLint pos_loc = glGetAttribLocation(m_program, "pos"); + // GLint angle_loc = glGetUniformLocation(app_state->program, "angle"); + + float *vertices = new float[6]{0.0f, 0.6f, -0.6f, -0.6f, 0.6f, -0.6f}; + + glEnableVertexAttribArray(pos_loc); + glVertexAttribPointer(pos_loc, 2, GL_FLOAT, GL_FALSE, 0, vertices); + glViewport(0, 0, width, height); + glClearColor(0.2f, 0.4f, 0.8f, 1.0f); + + m_xsurface = xdg_wm_base_get_xdg_surface( + WaylandState::GetInstance()->m_wm_base, m_wsurface); + if (m_xsurface == NULL) { + fprintf(stderr, "ERROR: could not get an xdg surface\n"); + exit(1); + } + + m_toplevel = xdg_surface_get_toplevel(m_xsurface); + if (m_toplevel == NULL) { + fprintf(stderr, "ERROR: could not get an xdg toplevel\n"); + exit(1); + } + + m_xsurface_listener.configure = xsurface_handle_configure; + xdg_surface_add_listener(m_xsurface, &m_xsurface_listener, this); + + m_toplevel_listener.configure = xtoplevel_handle_configure; + m_toplevel_listener.configure_bounds = xtoplevel_handle_configure_bounds; + m_toplevel_listener.close = xtoplevel_handle_close; + m_toplevel_listener.wm_capabilities = xtoplevel_handle_wm_capabilities; + xdg_toplevel_add_listener(m_toplevel, &m_toplevel_listener, this); + + wl_surface_commit(m_wsurface); + + m_running = true; +} + +WaylandWindowImpl::~WaylandWindowImpl() { m_running = false; } + +bool WaylandWindowImpl::Dispatch() { + return m_running && + wl_display_dispatch(WaylandState::GetInstance()->m_display) != -1; +} + +size_t WaylandWindowImpl::GetWidth() const { + return m_width; +} + +size_t WaylandWindowImpl::GetHeight() const { + return m_height; +} + +void WaylandWindowImpl::handle_frame_callback(void *data, + struct wl_callback *cb, + uint32_t time) { + UNUSED(time); + + auto window = reinterpret_cast(data); + if (!window->m_running) + return; + + wl_callback_destroy(cb); + auto frame_callback = wl_surface_frame(window->m_wsurface); + + window->m_frame_listener.done = handle_frame_callback; + + wl_callback_add_listener(frame_callback, &window->m_frame_listener, window); + + GLint angle_loc = glGetUniformLocation(window->m_program, "angle"); + + static float angle = 0.0f; + angle += 0.02f; + + glClear(GL_COLOR_BUFFER_BIT); + + glUniform1f(angle_loc, angle); + glDrawArrays(GL_TRIANGLES, 0, 3); + + Renderer::GetInstance()->SwapBuffers(window->m_egl_surface); +} + +void WaylandWindowImpl::xsurface_handle_configure(void *data, + struct xdg_surface *surface, + uint32_t serial) { + auto window = reinterpret_cast(data); + + xdg_surface_ack_configure(surface, serial); + + auto frame_callback = wl_surface_frame(window->m_wsurface); + printf("frame cb: %p\n", frame_callback); + + window->m_frame_listener.done = handle_frame_callback; + + int cb_res = wl_callback_add_listener(frame_callback, + &window->m_frame_listener, window); + + printf("mount callback listener: %d\n", cb_res); + + glClear(GL_COLOR_BUFFER_BIT); + Renderer::GetInstance()->SwapBuffers(window->m_egl_surface); +} + +void WaylandWindowImpl::xtoplevel_handle_configure( + void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height, + struct wl_array *states) { + UNUSED(toplevel); + auto window = reinterpret_cast(data); + printf("[DEBUG] received toplevel configure event\n"); + printf("[DEBUG] width = %d, height = %d\n", width, height); + printf("[DEBUG] %zu states\n", states->size); + + wl_egl_window_resize(window->m_egl_window, width, height, 0, 0); + glViewport(0, 0, width, height); + xdg_surface_set_window_geometry(window->m_xsurface, 0, 0, width, height); + window->m_width = width; + window->m_height = height; +} + +void WaylandWindowImpl::xtoplevel_handle_close(void *data, + struct xdg_toplevel *toplevel) { + auto window = reinterpret_cast(data); + UNUSED(toplevel); + printf("[DEBUG] window close request received\n"); + window->m_running = false; + + glDeleteProgram(window->m_program); + Renderer::GetInstance()->DestroyWindow(window->m_egl_surface, + window->m_egl_window); + xdg_toplevel_destroy(window->m_toplevel); + xdg_surface_destroy(window->m_xsurface); + wl_surface_destroy(window->m_wsurface); + // if (window->m_program) { + // eglMakeCurrent(renderer_display, m_egl_surface, m_egl_surface, + // m_egl_context); glDeleteProgram(m_program); glDeleteBuffers(1, &m_vbo); + // // if used eglMakeCurrent(renderer_display, EGL_NO_SURFACE, + // EGL_NO_SURFACE, EGL_NO_CONTEXT); m_program = 0; + // } + Renderer::GetInstance()->DestroyContext(window->m_egl_context); +} + +void WaylandWindowImpl::xtoplevel_handle_configure_bounds( + void *data, struct xdg_toplevel *toplevel, int32_t width, int32_t height) { + UNUSED(data); + UNUSED(toplevel); + printf("[DEBUG] window configure bounds, width = %d, height = %d\n", width, + height); +} + +void WaylandWindowImpl::xtoplevel_handle_wm_capabilities( + void *data, struct xdg_toplevel *toplevel, struct wl_array *capabilities) { + UNUSED(data); + UNUSED(toplevel); + UNUSED(capabilities); + printf("[DEBUG] window wm_capabilities event received\n"); +}