diff --git a/CMakeLists.txt b/CMakeLists.txt index 245873a..d2bab28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,8 @@ set(IMXUI_CORE_SOURCES src/XzpPackage.cpp src/XuiXmlWriter.cpp src/XurWriter.cpp + src/Audio.cpp + src/ImageManager.cpp ) @@ -29,6 +31,7 @@ set(IMXUI_BACKEND_SOURCES src/Skin.cpp ) + set(IMXUI_HEADERS include/ImXui/ImXui.h include/ImXui/RenderCommands.h @@ -38,12 +41,14 @@ set(IMXUI_HEADERS include/ImXui/Skin.h include/ImXui/XurWriter.h include/ImXui/XuiXmlWriter.h + include/ImXui/Audio.h ) add_library(ImXuiCore STATIC ${IMXUI_CORE_SOURCES} ${IMXUI_HEADERS}) -target_include_directories(ImXuiCore PUBLIC include compat/include) +target_include_directories(ImXuiCore PUBLIC include compat/include third_party) target_include_directories(ImXuiCore PRIVATE src) +target_include_directories(ImXuiCore PUBLIC include) set(IMXUI_HEADERS ${IMXUI_HEADERS} compat/include/xui.h compat/include/xuiapp.h compat/include/xuirender.h compat/include/xuiresource.h) @@ -51,13 +56,16 @@ set(IMXUI_HEADERS ${IMXUI_HEADERS} compat/include/xui.h compat/include/xuiapp.h add_library(ImXuiSoft STATIC src/FontEngine.cpp - src/ImageManager.cpp src/SoftwareRenderer.cpp ) target_include_directories(ImXuiSoft PUBLIC include) target_include_directories(ImXuiSoft PRIVATE src third_party) target_link_libraries(ImXuiSoft PUBLIC ImXuiCore) +if(NOT WIN32) + find_package(OpenGL) +endif() + if(WIN32) add_library(ImXui STATIC ${IMXUI_BACKEND_SOURCES} ${IMXUI_HEADERS}) target_include_directories(ImXui PUBLIC include) @@ -78,6 +86,13 @@ if(WIN32) endif() if(UNIX AND NOT APPLE) + if(OpenGL_FOUND) + target_sources(ImXuiSoft PRIVATE src/OpenGLRenderer.cpp) + target_compile_definitions(ImXuiCore PRIVATE IMXUI_HAS_OPENGL IMXUI_HAS_GLX) + target_compile_definitions(ImXuiSoft PRIVATE IMXUI_HAS_OPENGL IMXUI_HAS_GLX) + target_compile_definitions(ImXuiSoft PRIVATE IMXUI_HAS_OPENGL IMXUI_HAS_GLX) + target_link_libraries(ImXuiSoft PUBLIC OpenGL::GL) + endif() add_executable(NotepadDemo examples/NotepadDemo.cpp) target_link_libraries(NotepadDemo PRIVATE ImXuiSoft ImXuiCore X11) target_include_directories(NotepadDemo PRIVATE include) @@ -127,4 +142,5 @@ target_include_directories(XUITool PRIVATE include third_party/zlib) if(WIN32) install(TARGETS ImXui LIBRARY DESTINATION lib ARCHIVE DESTINATION lib) endif() + install(DIRECTORY include/ DESTINATION include) diff --git a/examples/NotepadDemo.cpp b/examples/NotepadDemo.cpp index 21985f1..6ef154c 100644 --- a/examples/NotepadDemo.cpp +++ b/examples/NotepadDemo.cpp @@ -55,7 +55,7 @@ #ifdef _WIN32 static ImXui::D3D11Renderer s_renderer; #else -static ImXui::SoftwareRenderer s_renderer; +static ImXui::OpenGLRenderer s_renderer; #endif static ImXui::Canvas s_canvas; static bool s_running = true; @@ -2773,30 +2773,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow) { CoUninitialize(); return 0; } - #else - -// 24-bit visuals expect BGRA byte order on x86, the renderer gives RGBA -static void swizzleToBlit() { - const uint8_t* fb = s_renderer.getFramebuffer(); - size_t n = s_blit.size(); - for (size_t i = 0; i < n; i += 4) { - s_blit[i] = fb[i + 2]; - s_blit[i + 1] = fb[i + 1]; - s_blit[i + 2] = fb[i]; - s_blit[i + 3] = 255; - } -} - -static void rebuildImage() { - if (s_img) XDestroyImage(s_img); - int w = s_renderer.getWidth(); - int h = s_renderer.getHeight(); - s_blit.resize((size_t)w * h * 4); - s_img = XCreateImage(s_dpy, s_visual, s_depth, ZPixmap, 0, - (char*)s_blit.data(), w, h, 32, w * 4); -} - static int x11Vk(KeySym ks, bool ctrl) { switch (ks) { case XK_Left: return VK_LEFT; @@ -2838,7 +2815,6 @@ static void handleXEvent(XEvent& ev, Atom wmDelete) { (ev.xconfigure.width != s_renderer.getWidth() || ev.xconfigure.height != s_renderer.getHeight())) { s_renderer.resize(ev.xconfigure.width, ev.xconfigure.height); - rebuildImage(); layoutUI(); s_needRender = true; } @@ -3000,8 +2976,6 @@ int main() { ButtonPressMask | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask); XMapWindow(s_dpy, s_win); - s_gc = XCreateGC(s_dpy, s_win, 0, nullptr); - s_curArrow = XCreateFontCursor(s_dpy, XC_left_ptr); s_curIBeam = XCreateFontCursor(s_dpy, XC_xterm); s_curHand = XCreateFontCursor(s_dpy, XC_hand2); @@ -3018,7 +2992,7 @@ int main() { XSetInputFocus(s_dpy, s_win, RevertToParent, CurrentTime); XFlush(s_dpy); - if (!s_renderer.init(nullptr, 1280, 800)) { + if (!s_renderer.init(s_dpy, 1280, 800)) { fprintf(stderr, "Renderer init failed\n"); return 1; } @@ -3038,9 +3012,7 @@ int main() { setFontIdx(1); layoutUI(); - scrollToCaret(); - s_rendererReady = true; - rebuildImage(); + scrollToCaret(); s_rendererReady = true; unsigned long lastBlink = nowMs(); while (s_running) { @@ -3071,17 +3043,11 @@ int main() { drawFileDialog(); s_renderer.endFrame(false); s_blinkOnly = false; - swizzleToBlit(); - XPutImage(s_dpy, s_win, s_gc, s_img, 0, 0, 0, 0, - s_renderer.getWidth(), s_renderer.getHeight()); - XFlush(s_dpy); } - usleep(5000); + if (!s_needRender && XPending(s_dpy) == 0) usleep(1000); } s_renderer.shutdown(); - XDestroyImage(s_img); - XFreeGC(s_dpy, s_gc); XDestroyWindow(s_dpy, s_win); XCloseDisplay(s_dpy); return 0; diff --git a/include/ImXui/Audio.h b/include/ImXui/Audio.h new file mode 100644 index 0000000..9f75b6e --- /dev/null +++ b/include/ImXui/Audio.h @@ -0,0 +1,52 @@ +#pragma once + +#include "ImXui.h" + +namespace ImXui { + +class AudioBackend : public ISoundSink { +public: + virtual ~AudioBackend() {} + virtual bool init() = 0; + virtual void shutdown() = 0; +}; + +class NullAudioBackend : public AudioBackend { +public: + bool init() override { return true; } + void shutdown() override {} + void playCue(const std::wstring&, const std::wstring&, const std::wstring&, float, bool) override {} + void stopCue(const std::wstring&) override {} +}; + +#if defined(_WIN32) +class WinMMAudioBackend : public AudioBackend { +public: + bool init() override; + void shutdown() override; + void playCue(const std::wstring&, const std::wstring&, const std::wstring&, float, bool) override; + void stopCue(const std::wstring&) override; +}; +#endif + +#if defined(__APPLE__) +class CoreAudioBackend : public AudioBackend { +public: + bool init() override; + void shutdown() override; + void playCue(const std::wstring&, const std::wstring&, const std::wstring&, float, bool) override; + void stopCue(const std::wstring&) override; +}; +#endif + +#if defined(__linux__) +class ALSAAudioBackend : public AudioBackend { +public: + bool init() override; + void shutdown() override; + void playCue(const std::wstring&, const std::wstring&, const std::wstring&, float, bool) override; + void stopCue(const std::wstring&) override; +}; +#endif + +} diff --git a/include/ImXui/Renderer.h b/include/ImXui/Renderer.h index 2a8ff88..0a61238 100644 --- a/include/ImXui/Renderer.h +++ b/include/ImXui/Renderer.h @@ -3,6 +3,7 @@ #include "ImXui.h" #include "TextureManager.h" #include "Skin.h" +#include "RenderCommands.h" #include #include #include @@ -77,6 +78,61 @@ public: }; +class OpenGLRenderer : public Renderer, public IRenderCommandSink { +public: + bool loadSystemFont(const std::string& name); + float measureText(const std::wstring& text, float sizePx) const; + float lineHeight(float sizePx) const; + const uint8_t* getFramebuffer() const; + OpenGLRenderer(); + ~OpenGLRenderer(); + + bool init(void* nativeWindow, int width, int height) override; + void shutdown() override; + bool resize(int width, int height) override; + bool beginFrame(float clearR = 0, float clearG = 0, float clearB = 0, float clearA = 1) override; + void endFrame(bool vsync = true) override; + void renderScene(const Scene* scene) override; + void setTextureManager(TextureManager* mgr) override; + void setSkin(const Skin* skin) override; + const Skin* getSkin() const override; + bool loadFont(const wchar_t* path) override; + bool initTextRendering() override; + void shutdownTextRendering() override; + void drawTexturedQuad(float x, float y, float w, float h, const wchar_t* texturePath, + float r = 1, float g = 1, float b = 1, float a = 1) override; + void drawFigure(float x, float y, float w, float h, + const std::vector& points, bool closed, + float fillR, float fillG, float fillB, float fillA, + float strokeR, float strokeG, float strokeB, float strokeA, + float strokeWidth) override; + void drawColoredQuad(float x, float y, float w, float h, + float r, float g, float b, float a) override; + void drawTextString(float x, float y, float w, float h, + const std::wstring& text, const std::wstring& fontName, float fontSize, + float r = 1, float g = 1, float b = 1, float a = 1, + bool centered = false) override; + int getWidth() const override; + int getHeight() const override; + + void beginScene(float width, float height) override; + void drawColoredQuad(const Rect& rect, const Color& color, float opacity) override; + void drawTexturedQuad(const Rect& rect, const std::wstring& resource, const Color& color, float opacity) override; + void drawText(const Rect& rect, const std::wstring& text, const std::wstring& fontName, float fontSize, const Color& color, float opacity, bool centered) override; + void endScene() override; + void pushClip(const Rect& rect) override; + void popClip() override; + void drawNineGrid(const Rect& rect, const std::wstring& resource, const Color& color, float opacity, uint32_t leftInset, uint32_t topInset, uint32_t rightInset, uint32_t bottomInset, bool noCenter) override; + void drawFigure(const Rect& rect, const std::vector& points, bool closed, const Color& fillColor, const Color& strokeColor, float opacity, float strokeWidth, uint32_t blendMode) override; + void setBlendMode(uint32_t blendMode) override; + void drawCustom(const std::wstring& drawId, const Rect& rect, float opacity) override; + +struct Impl; +private: + Impl* m_impl; +}; + + class D3D11Renderer : public Renderer { public: D3D11Renderer(); diff --git a/include/ImXui/TextureManager.h b/include/ImXui/TextureManager.h index fc33011..0b4aeb1 100644 --- a/include/ImXui/TextureManager.h +++ b/include/ImXui/TextureManager.h @@ -1,5 +1,6 @@ #pragma once +#include "ImageManager.h" #include #include #include @@ -20,13 +21,14 @@ public: void setBasePath(const std::string& path) { m_basePath = path; } const std::string& getBasePath() const { return m_basePath; } + const ImageData* loadImage(const std::string& relativePath); ID3D11ShaderResourceView* load(const std::string& relativePath); - bool getSize(const std::string& relativePath, int& w, int& h) const; private: ID3D11Device* m_device; std::string m_basePath; + ImageManager m_images; std::unordered_map m_cache; std::unordered_map m_widths; std::unordered_map m_heights; diff --git a/src/Audio.cpp b/src/Audio.cpp new file mode 100644 index 0000000..5a7e555 --- /dev/null +++ b/src/Audio.cpp @@ -0,0 +1,61 @@ +#include "ImXui/Audio.h" + +#if defined(_WIN32) +#include +#include +#pragma comment(lib, "winmm.lib") +#endif + +#if defined(__APPLE__) +#include +#endif + +#if defined(__linux__) +#include +#endif + +namespace ImXui { + +#if defined(_WIN32) +bool WinMMAudioBackend::init() { return true; } +void WinMMAudioBackend::shutdown() { stopCue(std::wstring()); } +void WinMMAudioBackend::playCue(const std::wstring& cue, const std::wstring&, const std::wstring&, float, bool loop) { + if (cue.empty()) return; + DWORD flags = SND_FILENAME | SND_ASYNC | SND_NODEFAULT; + if (loop) flags |= SND_LOOP; + PlaySoundW(cue.c_str(), nullptr, flags); +} +void WinMMAudioBackend::stopCue(const std::wstring&) { PlaySoundW(nullptr, nullptr, 0); } +#endif + +#if defined(__APPLE__) +bool CoreAudioBackend::init() { return true; } +void CoreAudioBackend::shutdown() {} +void CoreAudioBackend::playCue(const std::wstring& cue, const std::wstring&, const std::wstring&, float, bool) { + if (cue.empty()) return; + CFURLRef url = CFURLCreateFromFileSystemRepresentation(nullptr, + reinterpret_cast(cue.c_str()), cue.size() * sizeof(wchar_t), false); + if (!url) return; + SystemSoundID sound = 0; + if (AudioServicesCreateSystemSoundID(url, &sound) == kAudioServicesNoError) { + AudioServicesPlaySystemSound(sound); + AudioServicesDisposeSystemSoundID(sound); + } + CFRelease(url); +} +void CoreAudioBackend::stopCue(const std::wstring&) {} +#endif + +#if defined(__linux__) +bool ALSAAudioBackend::init() { return true; } +void ALSAAudioBackend::shutdown() {} +void ALSAAudioBackend::playCue(const std::wstring& cue, const std::wstring&, const std::wstring&, float, bool) { + if (cue.empty()) return; + std::string path(cue.begin(), cue.end()); + std::string command = "aplay -q \"" + path + "\" >/dev/null 2>&1 &"; + std::system(command.c_str()); +} +void ALSAAudioBackend::stopCue(const std::wstring&) {} +#endif + +} diff --git a/src/OpenGLRenderer.cpp b/src/OpenGLRenderer.cpp new file mode 100644 index 0000000..5c7dcf0 --- /dev/null +++ b/src/OpenGLRenderer.cpp @@ -0,0 +1,223 @@ +#include "ImXui/Renderer.h" +#include "ImXui/RenderCommands.h" +#include "ImXui/ImageManager.h" +#include "ImXui/FontEngine.h" +#if defined(IMXUI_HAS_OPENGL) +#define GL_GLEXT_PROTOTYPES 1 +#include +#include +#if defined(IMXUI_HAS_GLX) + +#include +#include +#endif +#endif +#include +#include +#include +#include +#include + +namespace ImXui { +struct OpenGLRenderer::Impl { + int width, height; + TextureManager* textureManager; + const Skin* skin; + ImageManager images; + FontEngine font; + std::map textures; + std::map textTextures; + unsigned white; + bool initialized; +#if defined(IMXUI_HAS_OPENGL) + unsigned program, vao, vbo; + int projection; + std::vector vertices; + unsigned batchTexture; + float batchBlendSrc, batchBlendDst; +#endif +#if defined(IMXUI_HAS_GLX) + Display* display; + GLXDrawable drawable; +#endif + Impl() : width(0), height(0), textureManager(nullptr), skin(nullptr), white(0), initialized(false) +#if defined(IMXUI_HAS_OPENGL) + , program(0), vao(0), vbo(0), projection(-1), batchTexture(0), batchBlendSrc(0), batchBlendDst(0) +#endif +#if defined(IMXUI_HAS_GLX) + , display(nullptr), drawable(0) +#endif + {} +}; + +namespace { +static std::string narrow(const std::wstring& s) { return std::string(s.begin(), s.end()); } +#if defined(IMXUI_HAS_OPENGL) +static unsigned compileShader(unsigned type, const char* source) { + unsigned shader = glCreateShader(type); + glShaderSource(shader, 1, &source, nullptr); + glCompileShader(shader); + int ok = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); + if (!ok) { glDeleteShader(shader); return 0; } + return shader; +} +static unsigned createProgram() { + static const char* vs = "attribute vec2 a_position; attribute vec2 a_uv; attribute vec4 a_color; varying vec2 v_uv; varying vec4 v_color; uniform vec2 u_size; void main(){ vec2 p=a_position/u_size*2.0-1.0; gl_Position=vec4(p.x,-p.y,0.0,1.0); v_uv=a_uv; v_color=a_color; }"; + static const char* fs = "uniform sampler2D u_texture; varying vec2 v_uv; varying vec4 v_color; void main(){ gl_FragColor=texture2D(u_texture,v_uv)*v_color; }"; + unsigned v = compileShader(GL_VERTEX_SHADER, vs), f = compileShader(GL_FRAGMENT_SHADER, fs); + if (!v || !f) { if (v) glDeleteShader(v); if (f) glDeleteShader(f); return 0; } + unsigned p = glCreateProgram(); glAttachShader(p, v); glAttachShader(p, f); glBindAttribLocation(p, 0, "a_position"); glBindAttribLocation(p, 1, "a_uv"); glBindAttribLocation(p, 2, "a_color"); glLinkProgram(p); glDeleteShader(v); glDeleteShader(f); + int ok = 0; glGetProgramiv(p, GL_LINK_STATUS, &ok); if (!ok) { glDeleteProgram(p); return 0; } return p; +} +static unsigned makeTexture(const ImageData& image) { + unsigned texture = 0; glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.w, image.h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image.rgba.data()); return texture; +} +#endif +} + +OpenGLRenderer::OpenGLRenderer() : m_impl(new Impl()) {} +OpenGLRenderer::~OpenGLRenderer() { shutdown(); delete m_impl; } + +bool OpenGLRenderer::init(void* window, int w, int h) { + shutdown(); if (w <= 0 || h <= 0) return false; m_impl->width = w; m_impl->height = h; +#if defined(IMXUI_HAS_OPENGL) +#if defined(IMXUI_HAS_GLX) + m_impl->display = static_cast(window); m_impl->drawable = m_impl->display ? glXGetCurrentDrawable() : 0; +#else + (void)window; +#endif + m_impl->program = createProgram(); if (!m_impl->program) return false; + glGenVertexArrays(1, &m_impl->vao); glGenBuffers(1, &m_impl->vbo); + glBindVertexArray(m_impl->vao); glBindBuffer(GL_ARRAY_BUFFER, m_impl->vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(RendererVertex2D) * 4096, nullptr, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(0); glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(RendererVertex2D), (void*)0); + glEnableVertexAttribArray(1); glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(RendererVertex2D), (void*)(sizeof(float) * 2)); + glEnableVertexAttribArray(2); glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(RendererVertex2D), (void*)(sizeof(float) * 4)); + m_impl->projection = glGetUniformLocation(m_impl->program, "u_size"); + glUseProgram(m_impl->program); glUniform1i(glGetUniformLocation(m_impl->program, "u_texture"), 0); + ImageData white; white.w = 1; white.h = 1; white.rgba.assign(4, 255); m_impl->white = makeTexture(white); + m_impl->vertices.reserve(4096); m_impl->initialized = m_impl->white != 0; return m_impl->initialized; +#else + (void)window; return false; +#endif +} + +void OpenGLRenderer::shutdown() { +#if defined(IMXUI_HAS_OPENGL) + if (!m_impl->initialized && !m_impl->program) return; + glUseProgram(0); for (auto& x : m_impl->textures) if (x.second) glDeleteTextures(1, &x.second); for (auto& x : m_impl->textTextures) if (x.second) glDeleteTextures(1, &x.second); if (m_impl->white) glDeleteTextures(1, &m_impl->white); if (m_impl->vbo) glDeleteBuffers(1, &m_impl->vbo); if (m_impl->vao) glDeleteVertexArrays(1, &m_impl->vao); if (m_impl->program) glDeleteProgram(m_impl->program); +#endif + m_impl->textures.clear(); m_impl->textTextures.clear(); m_impl->white = 0; m_impl->initialized = false; +#if defined(IMXUI_HAS_OPENGL) + m_impl->program = m_impl->vao = m_impl->vbo = 0; m_impl->vertices.clear(); +#endif +} + +bool OpenGLRenderer::resize(int w, int h) { if (w <= 0 || h <= 0) return false; m_impl->width = w; m_impl->height = h; +#if defined(IMXUI_HAS_OPENGL) + if (!m_impl->initialized) return false; glViewport(0, 0, w, h); +#endif + return true; +} + +bool OpenGLRenderer::beginFrame(float r, float g, float b, float a) { +#if defined(IMXUI_HAS_OPENGL) + if (!m_impl->initialized) return false; glViewport(0, 0, m_impl->width, m_impl->height); glClearColor(r, g, b, a); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(m_impl->program); glUniform2f(m_impl->projection, (float)m_impl->width, (float)m_impl->height); glBindVertexArray(m_impl->vao); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); m_impl->vertices.clear(); m_impl->batchTexture = 0; return true; +#else + return false; +#endif +} + +void OpenGLRenderer::endFrame(bool vsync) { +#if defined(IMXUI_HAS_OPENGL) + if (!m_impl->vertices.empty()) { glBindBuffer(GL_ARRAY_BUFFER, m_impl->vbo); glBufferSubData(GL_ARRAY_BUFFER, 0, (GLsizeiptr)(m_impl->vertices.size() * sizeof(RendererVertex2D)), m_impl->vertices.data()); glBindTexture(GL_TEXTURE_2D, m_impl->batchTexture); glDrawArrays(GL_TRIANGLES, 0, (GLsizei)m_impl->vertices.size()); m_impl->vertices.clear(); } +#if defined(IMXUI_HAS_GLX) + if (vsync && m_impl->display && m_impl->drawable) glXSwapBuffers(m_impl->display, m_impl->drawable); +#else + (void)vsync; +#endif +#else + (void)vsync; +#endif +} + +void OpenGLRenderer::renderScene(const Scene* s) { if (s) emitRenderCommands(s, static_cast(this)); } +void OpenGLRenderer::setTextureManager(TextureManager* m) { m_impl->textureManager = m; } +void OpenGLRenderer::setSkin(const Skin* s) { m_impl->skin = s; } +const Skin* OpenGLRenderer::getSkin() const { return m_impl->skin; } +bool OpenGLRenderer::loadFont(const wchar_t* p) { return p && m_impl->font.loadFontFile(narrow(p)); } +bool OpenGLRenderer::loadSystemFont(const std::string& n) { return m_impl->font.loadSystemFont(n); } +float OpenGLRenderer::measureText(const std::wstring& t, float s) const { return m_impl->font.measureText(t, s); } +float OpenGLRenderer::lineHeight(float s) const { return m_impl->font.lineHeight(s); } +const uint8_t* OpenGLRenderer::getFramebuffer() const { return nullptr; } +bool OpenGLRenderer::initTextRendering() { return m_impl->font.isLoaded() || m_impl->font.loadSystemFont(""); } +void OpenGLRenderer::shutdownTextRendering() {} + +#if defined(IMXUI_HAS_OPENGL) +static void appendQuad(OpenGLRenderer::Impl* i, unsigned texture, float x, float y, float w, float h, float u0, float v0, float u1, float v1, float r, float g, float b, float a) { + if (i->batchTexture != texture && !i->vertices.empty()) { glBindBuffer(GL_ARRAY_BUFFER, i->vbo); glBufferSubData(GL_ARRAY_BUFFER, 0, (GLsizeiptr)(i->vertices.size() * sizeof(RendererVertex2D)), i->vertices.data()); glBindTexture(GL_TEXTURE_2D, i->batchTexture); glDrawArrays(GL_TRIANGLES, 0, (GLsizei)i->vertices.size()); i->vertices.clear(); } + i->batchTexture = texture; + RendererVertex2D v[6] = {{x,y,u0,v0,r,g,b,a},{x+w,y,u1,v0,r,g,b,a},{x+w,y+h,u1,v1,r,g,b,a},{x,y,u0,v0,r,g,b,a},{x+w,y+h,u1,v1,r,g,b,a},{x,y+h,u0,v1,r,g,b,a}}; + i->vertices.insert(i->vertices.end(), v, v + 6); +} +#endif + +void OpenGLRenderer::drawTexturedQuad(float x, float y, float w, float h, const wchar_t* p, float r, float g, float b, float a) { + if (!p) return; std::string path(p, p + wcslen(p)); const ImageData* image = m_impl->images.load(path); +#if defined(IMXUI_HAS_OPENGL) + if (!m_impl->initialized || !image || !image->valid()) return; unsigned texture = 0; auto it = m_impl->textures.find(path); if (it != m_impl->textures.end()) texture = it->second; if (!texture) { texture = makeTexture(*image); m_impl->textures[path] = texture; } appendQuad(m_impl, texture, x, y, w, h, 0, 0, 1, 1, r, g, b, a); +#else + (void)x; (void)y; (void)w; (void)h; (void)r; (void)g; (void)b; (void)a; +#endif +} +void OpenGLRenderer::drawColoredQuad(float x, float y, float w, float h, float r, float g, float b, float a) { +#if defined(IMXUI_HAS_OPENGL) + if (m_impl->initialized) appendQuad(m_impl, m_impl->white, x, y, w, h, 0, 0, 1, 1, r, g, b, a); +#else + (void)x; (void)y; (void)w; (void)h; (void)r; (void)g; (void)b; (void)a; +#endif +} +void OpenGLRenderer::drawTextString(float x, float y, float w, float h, const std::wstring& text, const std::wstring& name, float size, float r, float g, float b, float a, bool centered) { +#if defined(IMXUI_HAS_OPENGL) + if (!m_impl->initialized || text.empty()) return; if (!m_impl->font.isLoaded() && !m_impl->font.loadSystemFont(narrow(name))) return; auto it = m_impl->textTextures.find(text + L"|" + name + L"|" + std::to_wstring((int)(size * 10))); unsigned texture = it == m_impl->textTextures.end() ? 0 : it->second; int tw = 0, th = 0; if (!texture) { std::vector pixels; if (!m_impl->font.renderText(text, size, pixels, tw, th)) return; ImageData image; image.w = tw; image.h = th; image.rgba.swap(pixels); texture = makeTexture(image); m_impl->textTextures[text + L"|" + name + L"|" + std::to_wstring((int)(size * 10))] = texture; } else { std::vector ignored; m_impl->font.renderText(text, size, ignored, tw, th); } float dx = centered ? x + std::max(0.0f, (w - tw) * .5f) : x, dy = centered ? y + std::max(0.0f, (h - th) * .5f) : y; appendQuad(m_impl, texture, dx, dy, (float)tw, (float)th, 0, 0, 1, 1, r, g, b, a); +#else + (void)x; (void)y; (void)w; (void)h; (void)text; (void)name; (void)size; (void)r; (void)g; (void)b; (void)a; (void)centered; +#endif +} +void OpenGLRenderer::drawFigure(float x, float y, float w, float h, const std::vector& points, bool closed, float fr, float fg, float fb, float fa, float sr, float sg, float sb, float sa, float sw) { +#if defined(IMXUI_HAS_OPENGL) + if (!m_impl->initialized || points.size() < 2) return; if (fa > .001f && points.size() > 2) { for (size_t n = 1; n + 1 < points.size(); ++n) { RendererVertex2D v[6] = {{x+points[0].x*w,y+points[0].y*h,0,0,fr,fg,fb,fa},{x+points[n].x*w,y+points[n].y*h,0,0,fr,fg,fb,fa},{x+points[n+1].x*w,y+points[n+1].y*h,0,0,fr,fg,fb,fa},{x+points[0].x*w,y+points[0].y*h,0,0,fr,fg,fb,fa},{x+points[n+1].x*w,y+points[n+1].y*h,0,0,fr,fg,fb,fa},{x+points[n].x*w,y+points[n].y*h,0,0,fr,fg,fb,fa}}; if (m_impl->batchTexture != m_impl->white && !m_impl->vertices.empty()) { glBindBuffer(GL_ARRAY_BUFFER, m_impl->vbo); glBufferSubData(GL_ARRAY_BUFFER,0,(GLsizeiptr)(m_impl->vertices.size()*sizeof(RendererVertex2D)),m_impl->vertices.data()); glBindTexture(GL_TEXTURE_2D,m_impl->batchTexture); glDrawArrays(GL_TRIANGLES,0,(GLsizei)m_impl->vertices.size()); m_impl->vertices.clear(); } m_impl->batchTexture=m_impl->white; m_impl->vertices.insert(m_impl->vertices.end(),v,v+6); } } if (sa > .001f && sw > 0) { glLineWidth(sw); std::vector v; for (size_t n=0; n + 1 < points.size(); ++n) { v.push_back({x+points[n].x*w,y+points[n].y*h,0,0,sr,sg,sb,sa}); v.push_back({x+points[n+1].x*w,y+points[n+1].y*h,0,0,sr,sg,sb,sa}); } if (closed) { v.push_back({x+points.back().x*w,y+points.back().y*h,0,0,sr,sg,sb,sa}); v.push_back({x+points[0].x*w,y+points[0].y*h,0,0,sr,sg,sb,sa}); } if (m_impl->batchTexture != m_impl->white && !m_impl->vertices.empty()) { glBindBuffer(GL_ARRAY_BUFFER,m_impl->vbo); glBufferSubData(GL_ARRAY_BUFFER,0,(GLsizeiptr)(m_impl->vertices.size()*sizeof(RendererVertex2D)),m_impl->vertices.data()); glBindTexture(GL_TEXTURE_2D,m_impl->batchTexture); glDrawArrays(GL_TRIANGLES,0,(GLsizei)m_impl->vertices.size()); m_impl->vertices.clear(); } m_impl->batchTexture=m_impl->white; glBindBuffer(GL_ARRAY_BUFFER,m_impl->vbo); glBufferSubData(GL_ARRAY_BUFFER,0,(GLsizeiptr)(v.size()*sizeof(RendererVertex2D)),v.data()); glDrawArrays(GL_LINES,0,(GLsizei)v.size()); } +#else + (void)x;(void)y;(void)w;(void)h;(void)points;(void)closed;(void)fr;(void)fg;(void)fb;(void)fa;(void)sr;(void)sg;(void)sb;(void)sa;(void)sw; +#endif +} +int OpenGLRenderer::getWidth() const { return m_impl->width; } +int OpenGLRenderer::getHeight() const { return m_impl->height; } +void OpenGLRenderer::beginScene(float, float) {} +void OpenGLRenderer::drawColoredQuad(const Rect& r, const Color& c, float o) { drawColoredQuad(r.x,r.y,r.w,r.h,c.r,c.g,c.b,c.a*o); } +void OpenGLRenderer::drawTexturedQuad(const Rect& r, const std::wstring& p, const Color& c, float o) { drawTexturedQuad(r.x,r.y,r.w,r.h,p.c_str(),c.r,c.g,c.b,c.a*o); } +void OpenGLRenderer::drawText(const Rect& r, const std::wstring& t, const std::wstring& f, float s, const Color& c, float o, bool center) { drawTextString(r.x,r.y,r.w,r.h,t,f,s,c.r,c.g,c.b,c.a*o,center); } +void OpenGLRenderer::endScene() {} +void OpenGLRenderer::pushClip(const Rect& r) { +#if defined(IMXUI_HAS_OPENGL) + glEnable(GL_SCISSOR_TEST); glScissor((int)r.x, (int)(m_impl->height-r.y-r.h), (int)r.w, (int)r.h); +#else + (void)r; +#endif +} +void OpenGLRenderer::popClip() { +#if defined(IMXUI_HAS_OPENGL) + glDisable(GL_SCISSOR_TEST); +#endif +} +void OpenGLRenderer::drawNineGrid(const Rect& r, const std::wstring& p, const Color& c, float o, uint32_t, uint32_t, uint32_t, uint32_t, bool) { drawTexturedQuad(r,p,c,o); } +void OpenGLRenderer::drawFigure(const Rect& r, const std::vector& points, bool closed, const Color& fill, const Color& stroke, float opacity, float strokeWidth, uint32_t blendMode) { setBlendMode(blendMode); drawFigure(r.x,r.y,r.w,r.h,points,closed,fill.r,fill.g,fill.b,fill.a*opacity,stroke.r,stroke.g,stroke.b,stroke.a*opacity,strokeWidth); } +void OpenGLRenderer::setBlendMode(uint32_t mode) { +#if defined(IMXUI_HAS_OPENGL) + glBlendFunc(GL_SRC_ALPHA, mode == 1 ? GL_ONE : GL_ONE_MINUS_SRC_ALPHA); +#else + (void)mode; +#endif +} +void OpenGLRenderer::drawCustom(const std::wstring&, const Rect&, float) {} +} diff --git a/src/TextureManager.cpp b/src/TextureManager.cpp index 49567cd..5c2f82b 100644 --- a/src/TextureManager.cpp +++ b/src/TextureManager.cpp @@ -10,9 +10,15 @@ namespace ImXui { bool TextureManager::init(ID3D11Device* device) { m_device = device; + m_images.setBasePath(m_basePath); return true; } +const ImageData* TextureManager::loadImage(const std::string& relativePath) { + m_images.setBasePath(m_basePath); + return m_images.load(relativePath); +} + void TextureManager::shutdown() { for (std::unordered_map::iterator it = m_cache.begin(); it != m_cache.end(); ++it) { if (it->second) it->second->Release(); @@ -29,6 +35,9 @@ ID3D11ShaderResourceView* TextureManager::load(const std::string& relativePath) auto it = m_cache.find(relativePath); if (it != m_cache.end()) return it->second; + const ImageData* decoded = loadImage(relativePath); + if (!decoded) return nullptr; + std::string fullPath; if (!m_basePath.empty()) { fullPath = m_basePath;