diff --git a/.gitignore b/.gitignore index a93da9a7..c61a328d 100644 --- a/.gitignore +++ b/.gitignore @@ -31,8 +31,9 @@ *.out *.app /.vs -/dev/msvc/.vs/olcPGE3 -/dev/msvc/olcPGE3/x64/Debug +/dev/msvc/.vs +/dev/msvc/x64 /dev/tests/build -/dev/msvc/olcPGE3_BuildSH/x64/Debug -/dev/msvc/olcPGE3/x64/Release +/dev/msvc/olcPGE3/x64 +/dev/msvc/olcPGE3_BuildSH/x64 + diff --git a/dev/msvc/olcPGE3.vcxproj b/dev/msvc/olcPGE3.vcxproj index 75d91f2a..13cc0792 100644 --- a/dev/msvc/olcPGE3.vcxproj +++ b/dev/msvc/olcPGE3.vcxproj @@ -29,26 +29,26 @@ Application true - v143 + v145 Unicode Application false - v143 + v145 true Unicode Application true - v143 + v145 Unicode Application false - v143 + v145 true Unicode @@ -137,6 +137,7 @@ _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true stdcpp20 + TurnOffAllWarnings Console @@ -179,6 +180,7 @@ + @@ -207,6 +209,8 @@ + + diff --git a/dev/msvc/olcPGE3.vcxproj.filters b/dev/msvc/olcPGE3.vcxproj.filters index 08ebcaab..396282f8 100644 --- a/dev/msvc/olcPGE3.vcxproj.filters +++ b/dev/msvc/olcPGE3.vcxproj.filters @@ -99,6 +99,9 @@ Header Files + + Header Files + @@ -146,5 +149,11 @@ Source Files + + Source Files + + + Source Files + \ No newline at end of file diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index e93c0b77..3c66e2a5 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -35,26 +35,26 @@ Application true - v143 + v145 Unicode Application false - v143 + v145 true Unicode Application true - v143 + v145 Unicode Application false - v143 + v145 true Unicode diff --git a/dev/msvc/olcPGE3_BuildSH/olcPixelGameEngine3.h b/dev/msvc/olcPGE3_BuildSH/olcPixelGameEngine3.h index 6b5e2868..c35cfbe7 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPixelGameEngine3.h +++ b/dev/msvc/olcPGE3_BuildSH/olcPixelGameEngine3.h @@ -1,12 +1,87 @@ #pragma once -// olc::PixelGameEngine 3.0 -// OneLoneCoder 2025 - // DO NOT EDIT THIS FILE! // This file is automatically generated. Changes to source should be made // through relevant component source file. +/* + olcPixelGameEngine3.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine v3.00 | + | "What do you need? Pixels... Lots of Pixels..." - javidx9 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + olc::PixelGameEngine is a single file, cross platform graphics and userinput + framework used for games, visualisations, algorithm exploration and learning. + It was developed by YouTuber "javidx9" as an assistive tool for many of his + videos. The goal of this project is to provide high speed graphics with + minimal project setup complexity, to encourage new programmers, younger people, + and anyone else that wants to make fun things. + + However, olc::PixelGameEngine is not a toy! It is a powerful and fast utility + capable of delivering high resolution, high speed, high quality applications + which behave the same way regardless of the operating system or platform. + + This file provides the core utility set of the olc::PixelGameEngine, including + window creation, keyboard/mouse input, main game thread, timing, pixel drawing + routines, image/sprite loading and drawing routines, and a bunch of utility + types to make rapid development of games/visualisations possible. + + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2025 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions or derivations of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce the above + copyright notice. This list of conditions and the following disclaimer must be + reproduced in the documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its contributors may + be used to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + TLDR: + You can do whatever you want with this code. Your only obligation is to credit in a + visible way to your end users in your running program that you used this code or a + derivative of it. If you don't edit this file, you are automatically in compliance with + OLC-3, as the message "OneLoneCoder.com" is present in the output window title bar. If + you compile to full screen, or remove the title bar, then you must display somewhere + something like "Powered by olcPixelGameEngine v3.00 - OneLoneCoder.com". That's it. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + X: https://www.x.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + Patreon: https://www.patreon.com/javidx9 +*/ + + #include #include #include @@ -104,13 +179,24 @@ #endif -#define OLC_MOUSE_BUTTONS 3 +#define OLC_MULTIWINDOW_NO 1 +#define OLC_MULTIWINDOW_YES 2 + +#if !defined(OLC_MULTIWINDOW) + #define OLC_MULTIWINDOW OLC_MULTIWINDOW_NO +#endif + + +#define OLC_MOUSE_BUTTONS 5 #define OLC_GPU_MAX_VERTICES 8192 #define OLC_GPU_ERRORCHECK 0 #define LICENCE_DEFAULT "OneLoneCoder.com - Pixel Game Engine 3 - " +// De-Noise in MSVC (C++20) +#pragma warning(disable:4820) // Disable Padding Warnings + #if !defined(PGE_PIXEL_DECLARED) namespace olc @@ -168,7 +254,13 @@ namespace olc // Construction via individual channels inline constexpr Pixel(const uint8_t red, const uint8_t green, const uint8_t blue, const uint8_t alpha = 0xFF) - : a(alpha), b(blue), g(green), r(red) +#if PGE_PIXEL_LAYOUT == PGE_PIXEL_LAYOUT_RGBA + : r(red), g(green), b(blue), a(alpha) +#endif +#if PGE_PIXEL_LAYOUT == PGE_PIXEL_LAYOUT_ABGR + : a(alpha), b(blue), g(green), r(red) +#endif + { } // Construction via numeric assignment (e.g. #00DDBBFF) @@ -490,6 +582,12 @@ namespace olc return v_2d(std::floor(x), std::floor(y)); } + // Rounds both components accurately + inline constexpr v_2d round() const + { + return v_2d(std::round(x), std::round(y)); + } + // Rounds both components up inline constexpr v_2d ceil() const { @@ -508,6 +606,12 @@ namespace olc return v_2d(std::min(x, v.x), std::min(y, v.y)); } + // Returns 'element-wise' abs of this vector + inline constexpr v_2d abs() const + { + return v_2d(std::abs(x), std::abs(y)); + } + // Calculates scalar dot product between this and another vector inline constexpr auto dot(const v_2d& rhs) const { @@ -798,6 +902,17 @@ namespace olc me(2, 2) = 1; } + inline constexpr std::array m4x4() + { + auto& me = (*this); + return { + me(0,0), me(1,0), me(2,0), 0, + me(0,1), me(1,1), me(2,1), 0, + me(0,2), me(1,2), me(2,2), 0, + 0, 0, 0, 1 + }; + } + // Create translation matrix via components template inline constexpr void translate(const Q x, const Q y) @@ -983,30 +1098,44 @@ namespace olc // Transform a vector by this transform template - inline constexpr auto forward(const olc::v_2d& v) + inline constexpr auto forward(const olc::v_2d& v) const { return m_mForward * v; } + template + inline constexpr auto forwardRound(const olc::v_2d& v) const + { + return (m_mForward * v).round(); + } + // Transform a vector of v_2d by this transform template - inline constexpr auto forward(const std::vector>& v) + inline constexpr auto forward(const std::vector>& v) const { std::vector> o(v.size()); std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_mForward * i; }); return o; } + template + inline constexpr auto forwardRound(const std::vector>& v) const + { + std::vector> o(v.size()); + std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return (m_mForward * i).round(); }); + return o; + } + // Transform a vector by the inverse of this transform template - inline constexpr auto inverse(const olc::v_2d& v) + inline constexpr auto inverse(const olc::v_2d& v) const { return m_mInverse * v; } // Transform a vector by this transform template - inline constexpr auto inverse(const std::vector>& v) + inline constexpr auto inverse(const std::vector>& v) const { std::vector> o(v.size()); std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_mInverse * i; }); @@ -1164,6 +1293,8 @@ namespace olc bool InVRAM = true; }; + struct ImageRegion; + class Image { public: @@ -1180,7 +1311,7 @@ namespace olc // Returns read/write pointer to start of 1D stream of pixel data olc::Pixel* Data(); // [UNSAFE] Returns pixel at location - olc::Pixel& Pixel(const olc::vf2d& pos); + olc::Pixel& Pixel(const olc::vi2d& pos); // Returns how this image was configured upon creation const ImageConfig& GetConfig() const; // Return GPU Resource ID @@ -1189,10 +1320,16 @@ namespace olc void SetGPUID(const int32_t id); // Get underlying vector of pixels std::vector& GetPixels(); + + void Resize(const olc::vi2d& size); bool BoundToGPU() const; bool BoundToCPU() const; + olc::ImageRegion region(const olc::vf2d pos, const olc::vf2d& size); + olc::ImageRegion region(const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBL, const olc::vf2d& vBR); + + public: // Make friendly private later void BindGPU(); void BindCPU(); @@ -1205,10 +1342,88 @@ namespace olc bool onGPU = false; bool onCPU = true; }; + + struct ImageRegion + { + olc::Image& image; + olc::vf2d regionsize; + union + { + std::array coords; + olc::vf2d tl; + olc::vf2d tr; + olc::vf2d br; + olc::vf2d bl; + }; + + ImageRegion(olc::Image& i, const olc::vf2d& vTL = { 0,0 }, const olc::vf2d& vTR = { 1,0 }, const olc::vf2d& vBL = { 0,1 }, const olc::vf2d& vBR = { 1,1 }) + : image(i) + { + coords = { vTL, vTR, vBR, vBL }; + regionsize = (vBR - vTL) * image.Size(); + } + + ImageRegion& operator=(ImageRegion& o) + { + + } + }; + + } #define PGE_IMAGE_DECLARED 1 #endif +#if !defined(PGE_FONT_DECLARED) +namespace olc +{ + class PGEWindow; + + struct FontGlyph + { + // Region of an olc::Image that contains the glyph + // in bitmap format. Typically WHITE on TRANSPARENT... + // ...though have fun with crazy alternatives :P + olc::ImageRegion imgGlyph; + // Leading spacing in pixels before glyph + float spacing; + // Size of the glyph in pixels + olc::vf2d vPropSize; + // Size of the glyph in monospace format + olc::vf2d vMonoSize; + }; + + class Font + { + public: + Font() = default; + ~Font() = default; + + public: + // Image that contains all glyphs + olc::Image imgFont; + // All glyphs in the font + std::vector glyphs; + // Line height in pixels + float fLineHeight = 10.0f; + // Tab width in pixels + float fTabWidth = 32.0f; + }; + + // Predefined classic PGE font + inline olc::Font fontClassicPGE; + + namespace pgeguts + { + // Create the classic PGE font + void CreateClassicFont(olc::PGEWindow* pge); + } + +} + +#define PGE_FONT_DECLARED 1 +#endif + #if !defined(PGE_IMAGELOADER_IFACE_DECLARED) namespace olc { @@ -1256,6 +1471,7 @@ namespace olc enum class Task : uint8_t { DrawPolygon, + NullTask } task = Task::DrawPolygon; struct Vertex @@ -1419,9 +1635,13 @@ namespace olc public: // Device Stuff // Constructs a GPU Device interface - virtual bool CreateDevice(std::vector params, const RendererConfig& cfg) = 0; + virtual bool CreateDevice(std::vector os_win_id, const RendererConfig& cfg) = 0; // Destroys a GPU device interface virtual bool DestroyDevice() = 0; + // If applicable, retarget the rendering context + virtual bool RetargetDevice(std::vector os_win_id) = 0; + // Prepare an OS rendering target + virtual bool PrepareWindowTarget(std::vector os_win_id) = 0; public: // Texture Resource Stuff // Allocates a new texture resource in VRAM, returns handle @@ -1453,7 +1673,7 @@ namespace olc // Configures defaults prior to drawing virtual bool DisplayPrepare() = 0; // Displays the final output - virtual bool DisplayDraw(bool bVerticalSyncNow = false) = 0; + virtual bool DisplayDraw(std::vector os_win_id, bool bVerticalSyncNow = false) = 0; protected: @@ -1481,64 +1701,165 @@ namespace olc // Associate this drawing toolbox with a renderer void SetGPU(olc::gpu::Renderer* const renderer); + void ProcessGPUTasks(); public: // Sets the drawing target of this drawing toolbox void SetTarget(olc::Image& image); - // Clears transform stack and sets identity transform - void ClearTransform(); - - void SetTransform(const olc::tf2d& trans); - - olc::tf2d& GetTransform(); - - void ProcessGPUTasks(); - - protected: - // Checks residency of image resource, and brings it to cpu RAM for r/w - void PrepareTargetForSW(); - // Checks residency of image resource, and brings it to gpu VRAM for r/w - void PrepareTargetForHW(); - - // Checks residency of image resource, and brings it to cpu RAM for r/w - void PrepareImageForSW(olc::Image& image); - // Checks residency of image resource, and brings it to gpu VRAM for r/w - void PrepareImageForHW(olc::Image& image); - olc::Image* pTarget = nullptr; - olc::gpu::Renderer* pRenderer = nullptr; - olc::tf2d transformTarget; - olc::tf2d transformAffine; - olc::mf3d transformCombined; - std::vector vecGPUTasks; + public: // Affine Transformation (these affect all subsequent draw calls for this target) - void AffineReset(); - void AffineScale(const olc::vf2d& vScale); - void AffineOffset(const olc::vf2d& vOffset); - void AffineRotate(const float& fTheta, const olc::vf2d& vPoint = { 0,0 }); - - public: + void WorldReset(); + void WorldScale(const olc::vf2d& vScale); + void WorldOffset(const olc::vf2d& vOffset); + void WorldRotate(const float& fTheta, const olc::vf2d& vPoint = { 0,0 }); + void SetWorldTransform(const olc::tf2d& trans); + olc::tf2d& GetWorldTransform(); + olc::vf2d WorldToScreen(const olc::vf2d& v) const; + olc::vf2d ScreenToWorld(const olc::vf2d& v) const; + + public: // Primitive Drawing Functions // Plot a single pixel - void Pixel(const olc::vf2d& pos, const olc::Pixel col = olc::Colour::WHITE); + void Pixel( + const olc::vf2d& pos, + const olc::Pixel col = olc::Colour::WHITE); + // Read a pixel from an image (guarantees fresh) - olc::Pixel GetPixel(olc::Image& image, const olc::vf2d& pos); + olc::Pixel GetPixel( + olc::Image& image, + const olc::vf2d& pos); + + // Clear entire draw target to specific colour + void Clear(const olc::Pixel& col); - public: + public: // Shape Drawing Functions // Draws a single pixel wide line - GPUTask Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col = olc::Colour::WHITE); + const GPUTask& Line( + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::Pixel col = olc::Colour::WHITE); + // Draws a single pixel wide line with a gradient - GPUTask Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel c1, const olc::Pixel c2); + const GPUTask& Line( + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::Pixel c1, + const olc::Pixel c2); // Draws a rectangle outline - GPUTask Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::Colour::WHITE); + const GPUTask& Rect( + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel col = olc::Colour::WHITE); + // Draws a filled, single colour rectangle - GPUTask FillRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::Colour::WHITE); + const GPUTask& FilledRect( + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel col = olc::Colour::WHITE); + + // Draws a filled, multiple colour rectangle, with linear colour interpolation + const GPUTask& FilledRect( + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel colTL, + const olc::Pixel colTR, + const olc::Pixel colBL, + const olc::Pixel colBR); + - // Draws an image - GPUTask Image(olc::Image& image, const olc::vf2d& pos, const olc::vf2d& size); + //TexturedRect + + + //Circle + //FilledCircle + //ShadedCircle + //TexturedCircle + + //Ellipse + //FilledEllipse + //ShadedEllipse + //TexturedEllipse + + //RoundRect + //FilledRoundRect + //ShadedRoundRect + //TexturedRoundRect + + //Triangle + //FilledTriangle + //ShadedTriangle + //TexturedTriangle + + //Polygon + //FilledPolygon + //ShadedPolygon + //TexturedPolygon + + public: // Text Drawing Functions + // Draws a string at specified location in monospace font + const GPUTask& String( + const olc::vf2d& pos, + const std::string& text, + const olc::Pixel col = olc::Colour::WHITE, + const olc::vf2d& scale = { 1.0f, 1.0f }, + olc::Font& font = olc::fontClassicPGE); + + // Draws a string at specified location in proportional font + const GPUTask& StringProp( + const olc::vf2d& pos, + const std::string& text, + const olc::Pixel col = olc::Colour::WHITE, + const olc::vf2d& scale = { 1.0f, 1.0f }, + olc::Font& font = olc::fontClassicPGE); + + olc::vf2d GetTextSize( + const std::string& text, + const bool bProportional = false, + const olc::vf2d& scale = { 1.0f, 1.0f }, + olc::Font& font = olc::fontClassicPGE); + + public: // Image Drawing Functions + // Draws a scaled image at specified location + const GPUTask& Image( + olc::ImageRegion image, + const olc::vf2d& pos, + const olc::vf2d& scale = { 1.0f, 1.0f }, + const olc::Pixel tint = olc::Colour::WHITE); + + // Draws an image rotated around a point at specified location + const GPUTask& ImageRotated( + olc::ImageRegion image, + const olc::vf2d& pos, + const float theta, + const olc::vf2d& center = { 0.0f, 0.0f }, + const olc::vf2d& scale = { 1.0f, 1.0f }, + const olc::Pixel tint = olc::Colour::WHITE); + + // Draws an image warped correctly to linearly fill a quadrilateral (formerly DrawWarped...) + const GPUTask& ImageQuad( + olc::ImageRegion image, + const olc::vf2d& vTL, + const olc::vf2d& vTR, + const olc::vf2d& vBR, + const olc::vf2d& vBL, + const olc::Pixel tint = olc::Colour::WHITE); + + // Draws an image warped correctly to linearly fill a quadrilateral (formerly DrawWarped...) + const GPUTask& ImageQuad( + olc::ImageRegion image, + const std::vector& vecPoints, + const olc::Pixel tint = olc::Colour::WHITE); + + // Draws an image scaled to a specified rectangular area + const GPUTask& ImageRect( + olc::ImageRegion image, + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel tint = olc::Colour::WHITE); @@ -1575,6 +1896,63 @@ namespace olc const std::vector& vTexCoords, olc::Image* const image, const olc::Pixel tint = olc::Colour::WHITE); + + GPUTask TaskTexturedPolygon( + GPUTask::Structure structure, + const std::vector& vPoints, + const std::vector& vZWs, + const std::vector& vColours, + const std::vector& vTexCoords, + olc::Image* const image, + const olc::Pixel tint = olc::Colour::WHITE); + + + public: // Precision drawing functions via software rasteriser + // Draws a single pixel wide line of fixed colour + void swLine( + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::Pixel col = olc::Colour::WHITE); + + // Draws a single pixel wide line with a gradient + void swLine( + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::Pixel c1, + const olc::Pixel c2); + + + + // Software rasteriser helper functions + + // Clips a line to a rectangular region, returns true if line is visible + bool swClipLine( + olc::vf2d& v0, + olc::vf2d& v1, + const olc::vf2d& vMin, + const olc::vf2d& vMax); + + + + + + + protected: + // Checks residency of image resource, and brings it to cpu RAM for r/w + void PrepareTargetForSW(); + // Checks residency of image resource, and brings it to gpu VRAM for r/w + void PrepareTargetForHW(); + + // Checks residency of image resource, and brings it to cpu RAM for r/w + void PrepareImageForSW(olc::Image& image); + // Checks residency of image resource, and brings it to gpu VRAM for r/w + void PrepareImageForHW(olc::Image& image); + + olc::Image* pTarget = nullptr; + olc::gpu::Renderer* pRenderer = nullptr; + olc::tf2d transformAffine; + + std::vector vecGPUTasks; }; } @@ -1601,7 +1979,8 @@ namespace olc namespace olc { // Forward declare for friendship - class Window; + class Window; + class PGEWindow; namespace hw @@ -1609,6 +1988,7 @@ namespace olc class Mouse { friend class olc::Window; + friend class olc::PGEWindow; public: Mouse() = default; @@ -1616,6 +1996,7 @@ namespace olc public: const Button& GetButton(const int nButton) const; const olc::vf2d& GetPosition() const; + int32_t GetWheel() const; protected: std::array buttons{}; @@ -1623,10 +2004,13 @@ namespace olc std::array buttons_old{}; olc::vf2d position; olc::vf2d position_in; + int32_t wheel_in; + int32_t wheel; private: void SetPosition(const olc::vf2d& pos); void SetButton(const int nButton, bool state); + void SetWheel(const int32_t w); void UpdateState(); }; } @@ -1653,6 +2037,11 @@ namespace olc class Renderer; } + namespace imload + { + class ImageLoader; + } + namespace hw { class Mouse; @@ -1666,27 +2055,22 @@ namespace olc public: Window(); virtual ~Window(); + + void LinkToHost(olc::host::Host* host); - void ConnecToHost(olc::host::Host* host); - - public: - // Return true if window is to continue - virtual bool OnUserCreate(); - // Return true if window is to continue - virtual bool OnUserUpdate(float fElapsedTime); - // Return true if window is to close - virtual bool OnUserDestroy(); + - private: + private: // These are called externally from the host // Set Mouse Device State - bool olc_OnMouseButton(const uint8_t nButton, const bool bPressed); - bool olc_OnMouseMove(const olc::vi2d& vMousePos); - bool olc_OnMouseWheel(const int32_t nScroll); - bool olc_OnMouseFocus(const bool bHasFocus); + virtual bool olc_OnMouseButton(const uint8_t nButton, const bool bPressed); + virtual bool olc_OnMouseMove(const olc::vi2d& vMousePos); + virtual bool olc_OnMouseWheel(const int32_t nScroll); + virtual bool olc_OnMouseFocus(const bool bHasFocus); // Set Window State - bool olc_OnWindowPosition(const olc::vi2d& vWindowPos); - bool olc_OnWindowSize(const olc::vi2d& vWindowSize); + virtual bool olc_OnWindowPosition(const olc::vi2d& vWindowPos); + virtual bool olc_OnWindowSize(const olc::vi2d& vWindowSize); + virtual bool olc_OnWindowClose(); // Set Keyboard State @@ -1694,25 +2078,22 @@ namespace olc public: bool olc_ShouldRemove() const; - bool olc_WindowUpdate(const float fElapsedTime, olc::gpu::Renderer* const gpu); + public: size_t GetUID() const; - const olc::vi2d& GetSize() const; - bool SetSize(const olc::vi2d& vSize); - - const olc::vi2d& GetPosition() const; - bool SetPosition(const olc::vi2d& vPosition); - - const std::string& GetTitle() const; - bool SetTitle(const std::string& sTitle); + const olc::vi2d& GetWindowSize() const; + bool SetWindowSize(const olc::vi2d& vSize); + const olc::vi2d& GetWindowPosition() const; + bool SetWindowPosition(const olc::vi2d& vPosition); - protected: - virtual bool olc_PrimaryWindowInit(); + const std::string& GetWindowTitle() const; + bool SetWindowTitle(const std::string& sTitle); protected: + bool bRequestToClose = false; bool bShouldRemove = false; protected: @@ -1721,17 +2102,12 @@ namespace olc olc::vi2d vWindowSize; std::string sFrameTitle; - private: - olc::vi2d volatile_vMousePos; - + protected: olc::host::Host* pHost = nullptr; - //olc::gpu::Renderer* pRenderer = nullptr; protected: - olc::Draw2D draw; - olc::Image imgPrimary; - olc::hw::Mouse mouse; + }; } #define PGE_WINDOW_DECLARED 1 @@ -1777,9 +2153,9 @@ namespace olc HostError GetLastError() const { return lastError; } public: - virtual bool StartSystemEventLoop() = 0; + virtual bool StartSystemEventLoop(bool bBlockIfPossible = false) = 0; virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) = 0; - + virtual bool CloseWindowFrame(olc::Window* pWindow) = 0; virtual bool UpdateWindowFrameTitle(olc::Window* pWindow) = 0; virtual std::vector GetHostWindowDescriptor(olc::Window* pWindow) = 0; @@ -1828,28 +2204,20 @@ namespace olc bool bAllowChildWindows = true; }; - class PixelGameEngine : protected Window + class PGEWindow : public Window { public: - PixelGameEngine(); - virtual ~PixelGameEngine(); - - public: - bool Construct(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize, bool bFullScreen = false); - bool Construct(const PGEConfig& cfg = PGEConfig{}); - + bool Create(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize); + public: - bool Start(); - - public: // The "Overridables" - // Called once when the engine is ready and the window is created - bool OnUserCreate() override; - // Called every frame while the engine is running - bool OnUserUpdate(float fElapsedTime) override; - // Called when something requests the engine shut down - bool OnUserDestroy() override; - - + // Return true if window is to continue + virtual bool OnUserCreate(); + // Return true if window is to continue + virtual bool OnUserUpdate(float fElapsedTime); + // Return true if window is to close + virtual bool OnUserDestroy(); + + public: // olc::Image Handling // Create an image resource bool CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg = olc::ImageConfig()); @@ -1864,14 +2232,54 @@ namespace olc // Destroy an image void DestroyImage(olc::Image& image); + public: + void LinkToRenderer(olc::gpu::Renderer* gpu); + void LinkToImageLoader(olc::imload::ImageLoader* imload); + + + public: + olc::Image& GetDefaultImage(); + olc::Draw2D& GetDraw(); + olc::hw::Mouse& GetMouse(); + + + protected: + bool olc_OnMouseMove(const olc::vi2d& vMousePos) override; + + public: + virtual bool olc_WindowUpdate(const float fElapsedTime); + protected: - bool olc_PrimaryWindowInit() override; + olc::Draw2D draw; + + + private: + olc::Image imgPrimary; + olc::gpu::Renderer* pRenderer = nullptr; + olc::imload::ImageLoader* pImageLoader = nullptr; + }; + + class PixelGameEngine : public PGEWindow + { + public: + PixelGameEngine(); + virtual ~PixelGameEngine(); + + public: + bool Construct(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize, bool bFullScreen = false); + bool Construct(const PGEConfig& cfg = PGEConfig{}); + + public: + bool Start(); + + public: // Child Windows + bool AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize); private: void EngineThread(); private: - std::deque> deqChildWindows; + std::deque> deqChildWindows; // Frame Timing & Overall Clocking std::chrono::steady_clock::time_point timeFrame1; @@ -1900,6 +2308,8 @@ namespace olc + + #if OLC_HOST == OLC_HOST_WINDOWS #if defined(UNICODE) || defined(_UNICODE) #define olcT(s) L##s @@ -1952,8 +2362,9 @@ namespace olc public: - bool StartSystemEventLoop(); + bool StartSystemEventLoop(bool bBlockIfPossible = false); bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen); + bool CloseWindowFrame(olc::Window* pWindow); bool UpdateWindowFrameTitle(olc::Window* pWindow); std::vector GetHostWindowDescriptor(olc::Window* pWindow); @@ -2175,7 +2586,7 @@ namespace olc void glBlendFunc(GLenum sfactor, GLenum dfactor); void glDepthFunc(GLenum func); void glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels); - + void glHint(GLenum target, GLenum mode); private: bool CheckError(const std::source_location loc = std::source_location::current()); @@ -2214,9 +2625,13 @@ namespace olc { public: // Device Stuff // Constructs a GPU Device interface - bool CreateDevice(std::vector params, const RendererConfig& cfg) override; + bool CreateDevice(std::vector os_win_id, const RendererConfig& cfg) override; // Destroys a GPU device interface bool DestroyDevice() override; + // If applicable, relocate the rendering context + bool RetargetDevice(std::vector os_win_id) override; + // Prepare an OS rendering target + bool PrepareWindowTarget(std::vector os_win_id) override; public: // Texture Resource Stuff @@ -2248,11 +2663,11 @@ namespace olc // Configures defaults prior to drawing virtual bool DisplayPrepare() override; // Displays the final output - virtual bool DisplayDraw(bool bVerticalSyncNow) override; + virtual bool DisplayDraw(std::vector os_win_id, bool bVerticalSyncNow) override; protected: // These may need some thinking about re multiple window - olc::apis::opengl::glDeviceContext_t glDeviceContext = 0; + //olc::apis::opengl::glDeviceContext_t glDeviceContext = 0; olc::apis::opengl::glRenderContext_t glRenderContext = 0; Shader_GLSL33 shaderDefault; @@ -2260,6 +2675,10 @@ namespace olc uint32_t nDefaultVA = 0; uint32_t nDefaultFBO = 0; olc::Image imgBlank; + olc::vf2d vTargetSize; + + uint32_t nCurrentTextureTarget = 0; + uint32_t nCurrentTextureSource = 0; }; } @@ -2286,13 +2705,13 @@ namespace olc #endif // In Code::Blocks -#if !defined(_WIN32_WINNT) - #ifdef HAVE_MSMF - #define _WIN32_WINNT 0x0600 // Windows Vista - #else - #define _WIN32_WINNT 0x0500 // Windows 2000 - #endif -#endif +//#if !defined(_WIN32_WINNT) +// #ifdef HAVE_MSMF +// #define _WIN32_WINNT 0x0600 // Windows Vista +// #else +// #define _WIN32_WINNT 0x0500 // Windows 2000 +// #endif +//#endif // Embrace MSVC superiority #pragma comment(lib, "gdiplus.lib") @@ -2342,7 +2761,7 @@ namespace olc -#if defined(OLC_PGE_APPLICATION) && !defined(PGE_HOST_IMPLEMENTED) +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HOST_IMPLEMENTED) #if OLC_HOST == OLC_HOST_WINDOWS namespace olc::host { @@ -2351,16 +2770,28 @@ namespace olc::host // Windows app needs an event loop somewhere. This is blocking of course. This loop handles // all windows created for this host. - bool Host_Windows_WinAPI::StartSystemEventLoop() + bool Host_Windows_WinAPI::StartSystemEventLoop(bool bBlockIfPossible) { - MSG msg; - while (GetMessage(&msg, NULL, 0, 0) > 0) + if (bBlockIfPossible) + { + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + else { - TranslateMessage(&msg); - DispatchMessage(&msg); + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } } - return true; + return true; } // Static linkage to lpfnWndProc - the hWnd is tagged with meta-info to get @@ -2416,7 +2847,8 @@ namespace olc::host // Define WindowClass - WNDCLASS wc = { 0 }; + WNDCLASSEX wc = { 0 }; + wc.cbSize = sizeof(WNDCLASSEX); wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; @@ -2427,7 +2859,7 @@ namespace olc::host wc.lpszMenuName = nullptr; wc.hbrBackground = nullptr; wc.lpszClassName = olcT("OLC_PIXEL_GAME_ENGINE3"); - RegisterClass(&wc); + RegisterClassEx(&wc); // Define window furniture DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; @@ -2453,12 +2885,21 @@ namespace olc::host AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); int width = rWndRect.right - rWndRect.left; int height = rWndRect.bottom - rWndRect.top; - pWindow->SetSize({width, height}); + pWindow->SetWindowSize(vWindowSize); // Create the actual OS window, return a handle HWND hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE3"), olcT(""), dwStyle, vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); + LONG_PTR lp = GetWindowLongPtr(hWnd, GWL_STYLE); + SetWindowLongPtr(hWnd, GWL_STYLE, lp | (WS_CAPTION | WS_SYSMENU | WS_POPUPWINDOW | WS_THICKFRAME)); + lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | (WS_EX_WINDOWEDGE)); + + //SetWindowPos(hWnd, NULL, vWinPos.x, vWinPos.y, width, height, SWP_SHOWWINDOW); + //ShowWindow(hWnd, 1); + //UpdateWindow(hWnd); + // Now... awkwardly, the above has already fired off some window messages // and they arent necessarily in a consistent order. Whereas one might // assume WM_CREATE would be the first, there are some others on more @@ -2471,7 +2912,7 @@ namespace olc::host mapUID2HWND.insert_or_assign(pWindow->GetUID(), hWnd); mapHWND2PTR.insert_or_assign(hWnd, pWindow); - pWindow->ConnecToHost(this); + //DragAcceptFiles(olc_hWnd, true); @@ -2479,12 +2920,18 @@ namespace olc::host return true; } + bool Host_Windows_WinAPI::CloseWindowFrame(olc::Window* pWindow) + { + DestroyWindow((HWND)GetHostWindowDescriptor(pWindow).front()); + return false; + } + bool Host_Windows_WinAPI::UpdateWindowFrameTitle(olc::Window* pWindow) { #ifdef UNICODE - SetWindowText(mapUID2HWND.at(pWindow->GetUID()), ConvertS2W(pWindow->GetTitle()).c_str()); + SetWindowText(mapUID2HWND.at(pWindow->GetUID()), ConvertS2W(pWindow->GetWindowTitle()).c_str()); #else - SetWindowText(mapUID2HWND.at(pWindow->GetUID()), sTitle.c_str()); + SetWindowText(mapUID2HWND.at(pWindow->GetUID()), pWindow->GetWindowTitle().c_str()); #endif return true; } @@ -2507,7 +2954,7 @@ namespace olc::host LRESULT Host_Windows_WinAPI::OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (!mapHWND2PTR.contains(hWnd)) - return false; + return DefWindowProc(hWnd, uMsg, wParam, lParam);; // Get target olc::Window const auto& window = mapHWND2PTR.at(hWnd); @@ -2526,19 +2973,22 @@ namespace olc::host int16_t iy = *(int16_t*)&y; // Tell window new mouse location window->olc_OnMouseMove(olc::vi2d{ ix, iy }); - return 0; + break; } - break; // case WM_MOVE: vWinPos = olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); ptrPGE->olc_UpdateWindowPos(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); return 0; case WM_SIZE: { window->olc_OnWindowSize(olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF)); - return 0; + break; + } + + case WM_MOUSEWHEEL: + { + window->olc_OnMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); + break; } - break; - // case WM_MOUSEWHEEL: ptrPGE->olc_UpdateMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); return 0; // case WM_MOUSELEAVE: ptrPGE->olc_UpdateMouseFocus(false); return 0; // case WM_SETFOCUS: ptrPGE->olc_UpdateKeyFocus(true); return 0; // case WM_KILLFOCUS: ptrPGE->olc_UpdateKeyFocus(false); return 0; @@ -2549,32 +2999,32 @@ namespace olc::host case WM_LBUTTONDOWN: { window->olc_OnMouseButton(0, true); - return 0; + break; } case WM_LBUTTONUP: { window->olc_OnMouseButton(0, false); - return 0; + break; } case WM_RBUTTONDOWN: { window->olc_OnMouseButton(1, true); - return 0; + break; } case WM_RBUTTONUP: { window->olc_OnMouseButton(1, false); - return 0; + break; } case WM_MBUTTONDOWN: { window->olc_OnMouseButton(2, true); - return 0; + break; } case WM_MBUTTONUP: { window->olc_OnMouseButton(2, false); - return 0; + break; } // case WM_DROPFILES: // { @@ -2614,12 +3064,19 @@ namespace olc::host // break; // // - // case WM_CLOSE: ptrPGE->olc_Terminate(); return 0; + case WM_CLOSE: + { + window->olc_OnWindowClose(); + break; + //return DefWindowProc(hWnd, uMsg, wParam, lParam); + } case WM_DESTROY: PostQuitMessage(0); DestroyWindow(hWnd); } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); } @@ -2628,7 +3085,7 @@ namespace olc::host #define PGE_HOST_IMPLEMENTED 1 #endif -#if defined(OLC_PGE_APPLICATION) && !defined(PGE_GPU_IMPLEMENTED) +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_GPU_IMPLEMENTED) #if OLC_GPU == OLC_GPU_OPENGL33 namespace olc { @@ -2712,7 +3169,7 @@ namespace olc::apis::opengl return bLoaded; } - bool gl::CheckError(const std::source_location loc) + bool gl::CheckError([[maybe_unused]] const std::source_location loc) { #if OLC_GPU_ERRORCHECK == 1 GLenum err; @@ -2855,6 +3312,12 @@ namespace olc::apis::opengl CheckError(); } + void gl::glHint(GLenum target, GLenum mode) + { + ::glHint(target, mode); + CheckError(); + } + GLuint gl::glCreateShader(GLenum type) { return _glCreateShader(type); @@ -3094,37 +3557,22 @@ namespace olc::gpu - bool Renderer_OGL33::CreateDevice(std::vector params, const RendererConfig& cfg) + bool Renderer_OGL33::CreateDevice(std::vector os_win_id, const RendererConfig& cfg) { config = cfg; - // Create OpenGL Device Context + #if OLC_HOST == OLC_HOST_WINDOWS - // "wgl*" all live in WinGDI - glDeviceContext = GetDC((HWND)(params[0])); - - PIXELFORMATDESCRIPTOR pfd = - { - sizeof(PIXELFORMATDESCRIPTOR), 1, - PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, - PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - PFD_MAIN_PLANE, 0, 0, 0, 0 - }; - - int pf = 0; - if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) + // Create OpenGL Device Context + if (!PrepareWindowTarget(os_win_id)) { - lastError = RendererError::InvalidDCPixelFormat; return false; } - if (!SetPixelFormat(glDeviceContext, pf, &pfd)) - { - lastError = RendererError::FailedToSetDCPixelFormat; - return false; - } + auto glDeviceContext = GetDC((HWND)(os_win_id[0])); + // Create OpenGL Render Context if (!(glRenderContext = wglCreateContext(glDeviceContext))) { lastError = RendererError::FailedToCreateRenderContext; @@ -3155,32 +3603,111 @@ namespace olc::gpu // Create "Default" Shader - shaderDefault.SetPixelShaderSource( - "#version 330 core\n" - "layout(location = 0) out vec4 pixel;\n" - "in vec2 oTex;\n" - "in vec4 oCol;\n" - "uniform sampler2D sprTex;\n" - "void main(){pixel = texture(sprTex, oTex) * oCol;}" - ); - - shaderDefault.SetVertexShaderSource( - "#version 330 core\n" - "layout(location = 0) in vec4 aPos;\n" - "layout(location = 1) in vec4 aCol;\n" - "layout(location = 2) in vec2 aTex;\n" - "uniform mat4 mvp;\n" - "uniform int is3d;\n" - "uniform vec4 tint;\n" - "out vec2 oTex;\n" - "out vec4 oCol;\n" - "void main(){ if(is3d!=0) {gl_Position = mvp * vec4(aPos.x, aPos.y, aPos.z, 1.0); oTex = aTex;} else {float p = 1.0 / aPos.z; gl_Position = p * vec4(aPos.x, aPos.y, 0.0, 1.0); oTex = p * aTex;} oCol = aCol * tint;}" - ); + shaderDefault.SetPixelShaderSource(R"( + #version 330 core + layout(location = 0) out vec4 pixel; + in vec2 oTex; + in vec4 oCol; + uniform sampler2D sprTex; + + void main() + { + pixel = texture(sprTex, oTex) * oCol; + } + )"); + + shaderDefault.SetVertexShaderSource(R"( + #version 330 core + layout(location = 0) in vec4 aPos; + layout(location = 1) in vec4 aCol; + layout(location = 2) in vec2 aTex; + uniform mat4 mvp; + uniform int drawtype; + uniform vec4 tint; + uniform vec2 target; + uniform vec2 invtarget; + out vec2 oTex; + out vec4 oCol; + + void main() + { + if(drawtype == 2) // 3D + { + gl_Position = mvp * vec4(aPos.x, aPos.y, aPos.z, 1.0); + oTex = aTex; + } + + else if(drawtype == 1) // 2D Line + { + float p = 1.0 / aPos.z; + gl_Position = p * vec4(vec2(2.0 * ((floor(aPos.xy) + 0.5) * invtarget) - 1.0), 0.0, 1.0); + oTex = aTex; + } + + else if(drawtype == 0) // 2D Polygon + { + float p = 1.0 / aPos.z; + gl_Position = p * vec4(vec2(2.0 * ((floor(aPos.xy)) * invtarget) - 1.0), 0.0, 1.0); + oTex = p * vec2(aTex.x, aTex.y); + } + + else // Balanced default + { + gl_Position = aPos; + oTex = aTex; + } + + oCol = aCol * tint; + } + )"); + + //shaderDefault.SetVertexShaderSource( + // "#version 330 core\n" + // "layout(location = 0) in vec4 aPos;\n" + // "layout(location = 1) in vec4 aCol;\n" + // "layout(location = 2) in vec2 aTex;\n" + // "uniform mat4 mvp;\n" + // "uniform int drawtype;\n" + // "uniform vec4 tint;\n" + // "uniform vec2 target;\n" + // "uniform vec2 invtarget;\n" + // "out vec2 oTex;\n" + // "out vec4 oCol;\n" + // "void main()\n" + // "{\n " + // " if(drawtype == 2)\n // 3D\n " + // " {\n " + // " gl_Position = mvp * vec4(aPos.x, aPos.y, aPos.z, 1.0);\n " + // " oTex = aTex;\n " + // " }\n " + // "\n" + // " else if(drawtype == 1)\n // Line\n " + // " {\n " + // " float p = 1.0 / aPos.z;\n " + // " gl_Position = p * vec4(2.0 * ((floor(aPos.x) + 0.5) * invtarget.x) - 1.0,2.0 * ((floor(aPos.y)+0.5) * invtarget.y) - 1.0, 0.0, 1.0);\n " + // " oTex = p * vec2(aTex.x, aTex.y);\n " + // " }\n " + // "" + // " else if(drawtype == 0)\n // Quad\n " + // " {\n " + // " float p = 1.0 / aPos.z;\n " + // " gl_Position = p * vec4(2.0 * ((floor(aPos.x)) * invtarget.x) - 1.0,2.0 * ((floor(aPos.y)) * invtarget.y) - 1.0, 0.0, 1.0);\n " + // " oTex = p * vec2(aTex.x, aTex.y);\n " + // " } else {gl_Position = aPos;}\n " + // " \n " + // " \n " + // " oCol = aCol * tint;\n " + // "}\n" + // //"void main(){ if(is3d!=0) {gl_Position = mvp * vec4(aPos.x, aPos.y, aPos.z, 1.0); oTex = aTex;} else {float p = 1.0 / aPos.z; gl_Position = mvp * (p * vec4(aPos.x, aPos.y, 0.0, 1.0)); oTex = p * aTex;} oCol = aCol * tint;}" + + //); shaderDefault.Compile(); shaderDefault.CreateUniform("mvp"); - shaderDefault.CreateUniform("is3d"); + shaderDefault.CreateUniform("drawtype"); shaderDefault.CreateUniform("tint"); + shaderDefault.CreateUniform("target"); + shaderDefault.CreateUniform("invtarget"); // Create "Default" Vertex Buffer / Vertex Attributes. This buffer is reused // for all drawing operations. It's possible future versions may allow the @@ -3245,8 +3772,9 @@ namespace olc::gpu // Unbind the FBO gl.glBindFramebuffer(36160U, 0); - glEnable(GL_TEXTURE_2D); // Turn on texturing - glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + gl.glEnable(GL_TEXTURE_2D); // Turn on texturing + gl.glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + gl.glEnable(GL_BLEND); lastError = RendererError::None; return true; @@ -3262,6 +3790,54 @@ namespace olc::gpu return false; } + bool Renderer_OGL33::RetargetDevice(std::vector os_win_id) + { +#if OLC_HOST == OLC_HOST_WINDOWS + auto glDeviceContext = GetDC((HWND)(os_win_id[0])); + + if (!wglMakeCurrent(glDeviceContext, glRenderContext)) + { + lastError = RendererError::FailedToSwitchRenderContext; + auto err = ::GetLastError(); + return false; + } + ReleaseDC((HWND)(os_win_id[0]), glDeviceContext); +#endif + + return true; + } + + bool Renderer_OGL33::PrepareWindowTarget(std::vector os_win_id) + { +#if OLC_HOST == OLC_HOST_WINDOWS + auto glDeviceContext = GetDC((HWND)(os_win_id[0])); + + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = 0; + if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) + { + lastError = RendererError::InvalidDCPixelFormat; + return false; + } + + if (!SetPixelFormat(glDeviceContext, pf, &pfd)) + { + lastError = RendererError::FailedToSetDCPixelFormat; + return false; + } + ReleaseDC((HWND)(os_win_id[0]), glDeviceContext); +#endif + + return true; + } + uint32_t Renderer_OGL33::CreateTexture(const olc::vi2d& vSize, const olc::ImageConfig& cfg) { auto& gl = olc::apis::opengl::gl::Get(); @@ -3300,6 +3876,7 @@ namespace olc::gpu gl.glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); #endif + std::cout << "Created Texture ID: " << id << " Size: " << vSize.x << "x" << vSize.y << "\n"; return id; } @@ -3331,8 +3908,23 @@ namespace olc::gpu { auto& gl = olc::apis::opengl::gl::Get(); + // If the requested source texture is currently attached as the render target, + // unbind the framebuffer to avoid sampling from a texture that's being written to. + if (texid == nCurrentTextureTarget && texid != 0) + { +#if defined(OLC_GPU_ERRORCHECK) && OLC_GPU_ERRORCHECK == 1 + std::cout << "Warning ATS: Requested source is currently attached as target (" << texid << ") - unbinding FBO\n"; +#endif + gl.glBindFramebuffer(36160U, 0); + nCurrentTextureTarget = 0; + } + + //if (nCurrentTextureSource == texid) + // return true; + gl.glActiveTexture(0x84C0 + slot); // GL_TEXTURE0 gl.glBindTexture(GL_TEXTURE_2D, texid); + nCurrentTextureSource = texid; return true; } @@ -3340,20 +3932,51 @@ namespace olc::gpu { auto& gl = olc::apis::opengl::gl::Get(); + + // If the requested target texture is currently bound as a source, unbind it + // from all texture units to ensure we do not sample from a texture that's + // attached to the FBO (undefined behavior). + if (texid != 0 && texid == nCurrentTextureSource) + { +#if defined(OLC_GPU_ERRORCHECK) && OLC_GPU_ERRORCHECK == 1 + std::cout << "Warning ATT: Requested target is currently bound as source (" << texid << ") - unbinding texture units\n"; +#endif + // Unbind from a reasonable number of texture units (0..7) used by this renderer + for (int i = 0; i < 8; ++i) + { + gl.glActiveTexture(0x84C0 + i); + gl.glBindTexture(GL_TEXTURE_2D, 0); + } + // Reset to texture unit 0 + gl.glActiveTexture(0x84C0); + nCurrentTextureSource = 0; + } + if (texid == 0) { + // Unbind the FBO (bind default framebuffer) gl.glBindFramebuffer(36160U, 0); return true; - } - + } + // Bind FBO gl.glBindFramebuffer(36160U, nDefaultFBO); - // Allocate target buffers + + //gl.glEnable(GL_BLEND); + //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + // Allocate target buffers - pick the single attachment corresponding to 'slot' std::array attachments = { { 36064U, 36065U, 36066U, 36067U, 36068U, 36069U, 36070U, 36071U } }; - gl.glDrawBuffers(1, attachments.data()); - // Bind buffers to textures - gl.glFramebufferTexture2D(36160U, 36064U + slot, GL_TEXTURE_2D, texid, 0); + GLenum draw = attachments[slot]; + gl.glDrawBuffers(1, &draw); + // Bind buffers to texture + gl.glFramebufferTexture2D(36160U, 36064U + slot, GL_TEXTURE_2D, texid, 0); + + //glReadBuffer(36064U + slot); // GL_COLOR_ATTACHMENT0 + slot + + + nCurrentTextureTarget = texid; + return true; } @@ -3368,6 +3991,8 @@ namespace olc::gpu { auto& gl = olc::apis::opengl::gl::Get(); gl.glUseProgram(shaderDefault.GetShaderID()); + + return true; } @@ -3379,7 +4004,9 @@ namespace olc::gpu { case GPUTask::Task::DrawPolygon: { + gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + //gl.glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); if (task.pImage == nullptr) AssignTextureSource(0, imgBlank.GetGPUID()); @@ -3393,11 +4020,10 @@ namespace olc::gpu // Copy data from CPU to GPU gl.glBufferData(0x8892, sizeof(GPUTask::Vertex) * task.vertexBuffer.size(), task.vertexBuffer.data(), 0x88E0); - // Shader: Configure Rendering Mode - gl.glUniform1i(shaderDefault.GetUniform("is3d"), 0); + // Shader: Apply MVP Matrix - gl.glUniformMatrix4fv(shaderDefault.GetUniform("mvp"), 1, false, task.mvpMatrix.data()); + //gl.glUniformMatrix4fv(shaderDefault.GetUniform("mvp"), 1, true, task.mvpMatrix.data()); // Shader: Apply Global Tint float f[4] = { @@ -3408,34 +4034,47 @@ namespace olc::gpu }; gl.glUniform4fv(shaderDefault.GetUniform("tint"), 1, f); - // Apply Culling modes - if (task.cullmode == GPUTask::CullMode::None) - { - gl.glCullFace(GL_FRONT); - gl.glDisable(GL_CULL_FACE); - } - else if (task.cullmode == GPUTask::CullMode::ClockWise) - { - gl.glCullFace(GL_FRONT); - gl.glEnable(GL_CULL_FACE); - } - else if (task.cullmode == GPUTask::CullMode::CounterClockWise) - { - gl.glCullFace(GL_BACK); - gl.glEnable(GL_CULL_FACE); - } - - // Apply Depth Testing (if required) - if (task.bDepth) - gl.glEnable(GL_DEPTH_TEST); + f[0] = 64.0f; + f[1] = 64.0f; + gl.glUniform2fv(shaderDefault.GetUniform("target"), 1, vTargetSize.a().data()); + gl.glUniform2fv(shaderDefault.GetUniform("invtarget"), 1, ((1.0f / vTargetSize)).a().data()); + // Apply Culling modes + //if (task.cullmode == GPUTask::CullMode::None) + //{ + // gl.glCullFace(GL_FRONT); + // gl.glDisable(GL_CULL_FACE); + //} + //else if (task.cullmode == GPUTask::CullMode::ClockWise) + //{ + // gl.glCullFace(GL_FRONT); + // gl.glEnable(GL_CULL_FACE); + //} + //else if (task.cullmode == GPUTask::CullMode::CounterClockWise) + //{ + // gl.glCullFace(GL_BACK); + // gl.glEnable(GL_CULL_FACE); + //} + + //// Apply Depth Testing (if required) + //if (task.bDepth) + // gl.glEnable(GL_DEPTH_TEST); + + gl.glEnable(GL_BLEND); + //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + gl.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // Draw the thing! if (task.bWireframe) { + // Shader: Configure Rendering Mode + gl.glUniform1i(shaderDefault.GetUniform("drawtype"), 1); gl.glDrawArrays(GL_LINE_LOOP, 0, (GLsizei)task.vertexBuffer.size()); } else { + // Shader: Configure Rendering Mode + gl.glUniform1i(shaderDefault.GetUniform("drawtype"), 0); + if (task.structure == GPUTask::Structure::Fan) gl.glDrawArrays(GL_TRIANGLE_FAN, 0, (GLsizei)task.vertexBuffer.size()); else if (task.structure == GPUTask::Structure::Strip) @@ -3444,6 +4083,8 @@ namespace olc::gpu gl.glDrawArrays(GL_TRIANGLES, 0, (GLsizei)task.vertexBuffer.size()); else if (task.structure == GPUTask::Structure::Line) gl.glDrawArrays(GL_LINES, 0, (GLsizei)task.vertexBuffer.size()); + else if (task.structure == GPUTask::Structure::Point) + gl.glDrawArrays(GL_POINTS, 0, (GLsizei)task.vertexBuffer.size()); } if (task.bDepth) @@ -3468,6 +4109,7 @@ namespace olc::gpu { auto& gl = olc::apis::opengl::gl::Get(); gl.glViewport(int(pos.x), int(pos.y), int(size.x), int(size.y)); + vTargetSize = size; return true; } @@ -3484,16 +4126,18 @@ namespace olc::gpu return false; } - bool Renderer_OGL33::DisplayDraw(bool bVerticalSyncNow) + bool Renderer_OGL33::DisplayDraw(std::vector os_win_id, bool bVerticalSyncNow) { auto& gl = olc::apis::opengl::gl::Get(); #if OLC_HOST == OLC_HOST_WINDOWS - SwapBuffers(glDeviceContext); + auto glDeviceContext = GetDC((HWND)(os_win_id[0])); + SwapBuffers(glDeviceContext); + ReleaseDC((HWND)(os_win_id[0]), glDeviceContext); #endif - return false; + return true; } @@ -3502,7 +4146,7 @@ namespace olc::gpu #define PGE_GPU_IMPLEMENTED 1 #endif -#if defined(OLC_PGE_APPLICATION) && !defined(PGE_DRAW2D_IMPLEMENTED) +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_DRAW2D_IMPLEMENTED) using namespace olc; Draw2D::Draw2D() @@ -3516,43 +4160,23 @@ void Draw2D::SetGPU(olc::gpu::Renderer* const renderer) void Draw2D::SetTarget(olc::Image& image) { + // Perform any outstanding tasks for current target ProcessGPUTasks(); + // Endure new target exists in GPU up to date + PrepareImageForHW(image); + // Store the target image pTarget = ℑ - // Create a transform to denormalise the image and present it - // in pixel space - transformTarget.scale(olc::vf2d(2.0f, 2.0f) / olc::vf2d(pTarget->Size())); - transformTarget.translate( - olc::vf2d( - -1.0f + (1.0f / float(pTarget->Size().x)), - -1.0f + (1.0f / float(pTarget->Size().y)) - )); - - AffineReset(); + // Reset Affine transform to unity + WorldReset(); + // Configure default render target pRenderer->AssignTextureTarget(0, pTarget->GetGPUID()); pRenderer->SetViewport({ 0,0 }, pTarget->Size()); } -void Draw2D::ClearTransform() -{ - transformTarget = olc::tf2d(); - transformAffine = olc::tf2d(); - transformCombined = olc::mf3d(); -} - -void Draw2D::SetTransform(const olc::tf2d& trans) -{ - transformTarget = trans; -} - -tf2d& olc::Draw2D::GetTransform() -{ - return transformAffine; -} - void olc::Draw2D::ProcessGPUTasks() { for (const auto& task : vecGPUTasks) @@ -3610,39 +4234,71 @@ void Draw2D::PrepareImageForHW(olc::Image& image) // Image resource is primed for CPU operations, send it to GPU pRenderer->WriteTexture(image.GetGPUID(), image); + if (&image == pTarget) + { + // If this image is also the current target, ensure renderer is updated + //SetTarget(image); + + // Store the target image + pTarget = ℑ + + // Reset Affine transform to unity + WorldReset(); + + // Configure default render target + pRenderer->AssignTextureTarget(0, pTarget->GetGPUID()); + pRenderer->SetViewport({ 0,0 }, pTarget->Size()); + } + // Image is now GPU bound image.BindGPU(); } } -void olc::Draw2D::AffineReset() +void olc::Draw2D::WorldReset() { transformAffine = olc::tf2d(); - transformCombined = transformAffine * transformTarget; } -void olc::Draw2D::AffineScale(const olc::vf2d& vScale) +void olc::Draw2D::WorldScale(const olc::vf2d& vScale) { transformAffine.scale(vScale); - transformCombined = transformAffine * transformTarget; } -void olc::Draw2D::AffineOffset(const olc::vf2d& vOffset) +void olc::Draw2D::WorldOffset(const olc::vf2d& vOffset) { transformAffine.translate(vOffset); - transformCombined = transformAffine * transformTarget; } -void olc::Draw2D::AffineRotate(const float& fTheta, const olc::vf2d& vPoint) +void olc::Draw2D::WorldRotate(const float& fTheta, const olc::vf2d& vPoint) { transformAffine.rotate(fTheta, vPoint); - transformCombined = transformAffine * transformTarget; +} + +void olc::Draw2D::SetWorldTransform(const olc::tf2d& trans) +{ + transformAffine = trans; +} + +olc::tf2d& olc::Draw2D::GetWorldTransform() +{ + return transformAffine; +} + +olc::vf2d olc::Draw2D::WorldToScreen(const olc::vf2d& v) const +{ + return transformAffine.forward(v); +} + +olc::vf2d olc::Draw2D::ScreenToWorld(const olc::vf2d& v) const +{ + return transformAffine.inverse(v); } void Draw2D::Pixel(const olc::vf2d& pos, const olc::Pixel col) { // Check if in bounds - olc::vf2d tpos = transformTarget.forward(pos); + olc::vf2d tpos = transformAffine.forward(pos); if (tpos.x >= 0 && tpos.y >= 0 && tpos.x < pTarget->Size().x && tpos.y < pTarget->Size().y) { PrepareTargetForSW(); @@ -3658,6 +4314,12 @@ olc::Pixel olc::Draw2D::GetPixel(olc::Image& image, const olc::vf2d& pos) return image.Pixel(pos); } +void olc::Draw2D::Clear(const olc::Pixel& col) +{ + //PrepareImageForHW(*pTarget); + pRenderer->ClearViewport(col, true, true); +} + GPUTask olc::Draw2D::TaskDrawPolygon(GPUTask::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) { GPUTask task; @@ -3676,14 +4338,19 @@ GPUTask olc::Draw2D::TaskDrawPolygon(GPUTask::Structure structure, const std::ve task.structure = structure; task.bWireframe = true; for (const auto& v : vPoints) - task.vertexBuffer.push_back({ v.x, v.y, 1.0f, 1.0f, colour, 0, 0, 0,0, 0, 0, 0, 0 }); + task.vertexBuffer.push_back({ v.x+0.0f, v.y+0.0f, 1.0f, 1.0f, colour, 0, 0, 0,0, 0, 0, 0, 0 }); task.tint = tint; return task; } GPUTask olc::Draw2D::TaskFillPolygon(GPUTask::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) { - return GPUTask(); + GPUTask task; + task.structure = structure; + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer.push_back({ vPoints[i].x, vPoints[i].y, 1.0f, 1.0f, vColours[i], 0, 0, 0, 0, 0, 0, 0, 0 }); + task.tint = tint; + return task; } GPUTask olc::Draw2D::TaskFillPolygon(GPUTask::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) @@ -3703,205 +4370,448 @@ GPUTask olc::Draw2D::TaskTexturedPolygon(GPUTask::Structure structure, const std task.vertexBuffer.push_back({ vPoints[i].x, vPoints[i].y, 1.0f, 1.0f, vColours[i], vTexCoords[i].x, vTexCoords[i].y, 0, 0, 0, 0, 0, 0}); task.pImage = image; task.tint = tint; + //task.mvpMatrix = transformCombined.m4x4(); + return task; +} + +GPUTask olc::Draw2D::TaskTexturedPolygon(GPUTask::Structure structure, const std::vector& vPoints, const std::vector& vZWs, const std::vector& vColours, const std::vector& vTexCoords, olc::Image* const image, const olc::Pixel tint) +{ + GPUTask task; + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer.push_back({ vPoints[i].x, vPoints[i].y, vZWs[i].x, vZWs[i].y, vColours[i], vTexCoords[i].x, vTexCoords[i].y, 0, 0, 0, 0, 0, 0}); + task.pImage = image; + task.tint = tint; return task; } -GPUTask Draw2D::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) + + +const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) { PrepareTargetForHW(); return vecGPUTasks.emplace_back( TaskDrawPolygon( GPUTask::Structure::Line, - transformCombined.transform({ p1, p2 }), + transformAffine.forward({p1, p2}), col, olc::Colour::WHITE )); } -GPUTask Draw2D::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel c1, const olc::Pixel c2) +const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel c1, const olc::Pixel c2) { PrepareTargetForHW(); return vecGPUTasks.emplace_back( TaskDrawPolygon( GPUTask::Structure::Line, - transformCombined.transform({ p1, p2 }), + transformAffine.forward({ p1, p2 }), { c1, c2 }, olc::Colour::WHITE - )); + )); } -GPUTask olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) { + // Right a big, hearty, F&^% you to OpenGL's Diamond Exit Strategy. It makes line drawing + // with OpenGL a smidge unreliable + + // Check if its axis aligned + if (std::abs(transformAffine.rotate()) < 0.0001f) + { + // SW Raster the line + } + PrepareTargetForHW(); return vecGPUTasks.emplace_back( TaskDrawPolygon( GPUTask::Structure::Fan, - transformCombined.transform({ { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }), + transformAffine.forward({ + olc::vf2d( pos.x + 0.0f, pos.y + 0.0f ), + olc::vf2d( pos.x + size.x +0.0f, pos.y + 0.0f ), + olc::vf2d( pos.x + size.x + 0.0f, pos.y + size.y + 0.0f ), + olc::vf2d( pos.x + 0.0f, pos.y + size.y + 0.0f ), + }), col, olc::Colour::WHITE )); } -GPUTask olc::Draw2D::FillRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) { PrepareTargetForHW(); return vecGPUTasks.emplace_back( TaskFillPolygon( GPUTask::Structure::Fan, - transformCombined.transform({ { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }), + transformAffine.forward({ { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }), + col, olc::Colour::WHITE )); } -GPUTask olc::Draw2D::Image(olc::Image& image, const olc::vf2d& pos, const olc::vf2d& size) +const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) { - // Ensure source image is up to date in VRAM - PrepareImageForHW(image); - PrepareTargetForHW(); return vecGPUTasks.emplace_back( - TaskTexturedPolygon( + TaskFillPolygon( GPUTask::Structure::Fan, - transformCombined.transform({ { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }), - { olc::Colour::WHITE, olc::Colour::WHITE, olc::Colour::WHITE, olc::Colour::WHITE }, - { {0,0}, {1,0}, {1,1}, {0,1} }, - &image + transformAffine.forward({ { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }), + { colTL, colTR, colBR, colBL }, + olc::Colour::WHITE )); } - -#define PGE_DRAW2D_IMPLEMENTED 1 -#endif - -#if defined(OLC_PGE_APPLICATION) && !defined(PGE_CORE_IMPLEMENTED) -namespace olc +const GPUTask& olc::Draw2D::String(const olc::vf2d& pos, const std::string& text, const olc::Pixel col, const olc::vf2d& scale, olc::Font& font) { - PixelGameEngine::PixelGameEngine() : Window() - { - } + PrepareTargetForHW(); - PixelGameEngine::~PixelGameEngine() - { - } + olc::vf2d spos = { 0.0f, 0.0f }; - bool PixelGameEngine::Construct(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize, bool bFullScreen) + for (auto c : text) { - config.vScreenSize = vScreenSize; - config.vPixelSize = vPixelSize; - config.bFullScreen = bFullScreen; + const auto& glyph = font.glyphs[c]; - return true; - } - - bool PixelGameEngine::Construct(const PGEConfig& cfg) - { - config = cfg; - - return false; + if (c == '\n') + { + spos.x = 0; + spos.y += font.fLineHeight * scale.y; + } + else if (c == '\t') + { + spos.x += font.fTabWidth * scale.x; + } + else + { + Draw2D::Image(font.glyphs[c].imgGlyph, pos + spos, scale, col); + spos.x += glyph.vMonoSize.x * scale.x; + } } - bool PixelGameEngine::Start() - { - // Initialise Host Interface - host = std::make_unique(); - - - // Link this olc::Window to a host resource - if (host) - host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); - - coreActive = true; - coreThread = std::thread(&PixelGameEngine::EngineThread, this); - - // This will block depending upon the system - host->StartSystemEventLoop(); + return vecGPUTasks.emplace_back(); +} - // Gracefully terminate the main engine thread - coreActive = false; - coreThread.join(); +const GPUTask& olc::Draw2D::StringProp(const olc::vf2d& pos, const std::string& text, const olc::Pixel col, const olc::vf2d& scale, olc::Font& font) +{ + PrepareTargetForHW(); - return true; - } + olc::vf2d spos = { 0.0f, 0.0f }; - bool PixelGameEngine::OnUserCreate() + for (auto c : text) { - return false; - } + const auto& glyph = font.glyphs[c]; - bool PixelGameEngine::OnUserUpdate(float fElapsedTime) - { - return false; + if (c == '\n') + { + spos.x = 0; + spos.y += font.fLineHeight * scale.y; + } + else if (c == '\t') + { + spos.x += font.fTabWidth * scale.x; + } + else + { + Draw2D::Image(font.glyphs[c].imgGlyph, pos + spos, scale, col); + spos.x += glyph.vPropSize.x * scale.x; + } } - bool PixelGameEngine::OnUserDestroy() - { - return false; - } + return vecGPUTasks.emplace_back(); +} + +olc::vf2d olc::Draw2D::GetTextSize(const std::string& text, const bool bProportional, const olc::vf2d& scale, olc::Font& font) +{ + olc::vf2d size = { 0, font.fLineHeight * scale.y }; + olc::vf2d pos = { 0, font.fLineHeight * scale.y }; - bool PixelGameEngine::CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg) + for (auto c : text) { - // Create CPU Image - if (!image.Create(size, cfg)) - return false; + const auto& glyph = font.glyphs[c]; - // Create GPU Image - auto id = gpu->CreateTexture(image.Size(), cfg); - if (id == 0) + if (c == '\n') + { + pos.y += font.fLineHeight * scale.y; + pos.x = 0; + } + else if (c == '\t') + { + pos.x += font.fTabWidth * scale.x; + } + else { - image.Create({ 0,0 }); - return false; + if(bProportional) + pos.x += glyph.vPropSize.x * scale.x; + else + pos.x += glyph.vMonoSize.x * scale.x; } - // Associate CPU object with GPU Resource - image.SetGPUID(id); - return true; + size = size.max(pos); } - bool PixelGameEngine::CreateImageFromFile(olc::Image& image, const std::string& sFileName, const ImageConfig& cfg) - { - if (imageloader->CreateImageFromFile(image, sFileName)) - { - // Image has loaded ok, and populated into pixel vector - // - // Create GPU Image - auto id = gpu->CreateTexture(image.Size(), cfg); - if (id == 0) - { - image.Create({ 0,0 }); - return false; - } - - // Associate CPU object with GPU Resource - image.SetGPUID(id); - return true; - } + return size; +} - return false; - } +const GPUTask& olc::Draw2D::Image(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) +{ + // Ensure source image is up to date in VRAM + PrepareImageForHW(image.image); + + PrepareTargetForHW(); - bool PixelGameEngine::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes, const ImageConfig& cfg) - { - return false; - } + olc::vf2d size = image.regionsize * scale; - bool PixelGameEngine::WriteImageToFile(const olc::Image& image, const std::string& sFileName) + return vecGPUTasks.emplace_back( + TaskTexturedPolygon( + GPUTask::Structure::Fan, + transformAffine.forwardRound({ { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }), + { tint, tint, tint, tint }, + // Tex coords are clockwise + { image.coords[0], image.coords[1], image.coords[2], image.coords[3]}, + &image.image + )); +} + +const GPUTask& olc::Draw2D::ImageRotated(olc::ImageRegion image, const olc::vf2d& pos, const float theta, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel tint) +{ + // Ensure source image is up to date in VRAM + PrepareImageForHW(image.image); + PrepareTargetForHW(); + + olc::vf2d size = image.regionsize * scale; + + std::vector vPoints(4); + vPoints[0] = (olc::vf2d(0.0f, 0.0f) - center) * scale; + vPoints[1] = (olc::vf2d(size.x, 0.0f) - center) * scale; + vPoints[2] = (size - center) * scale; + vPoints[3] = (olc::vf2d(0.0f, size.y) - center) * scale; + + float c = cos(theta), s = sin(theta); + for (int i = 0; i < 4; i++) + vPoints[i] = pos + olc::vf2d(vPoints[i].x * c - vPoints[i].y * s, vPoints[i].x * s + vPoints[i].y * c); + + return vecGPUTasks.emplace_back( + TaskTexturedPolygon( + GPUTask::Structure::Fan, + transformAffine.forward(vPoints), + { tint, tint, tint, tint}, + { image.coords[0], image.coords[1], image.coords[2], image.coords[3] }, + &image.image + )); +} + +const GPUTask& olc::Draw2D::ImageQuad(olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, const olc::vf2d& vBL, const olc::Pixel tint) +{ + // Ensure source image is up to date in VRAM + PrepareImageForHW(image.image); + PrepareTargetForHW(); + + + float rd = ((vBR.x - vTL.x) * (vTR.y - vBL.y) - (vTR.x - vBL.x) * (vBR.y - vTL.y)); + if (rd != 0) + { + rd = 1.0f / rd; + float rn = ((vTR.x - vBL.x) * (vTL.y - vBL.y) - (vTR.y - vBL.y) * (vTL.x - vBL.x)) * rd; + float sn = ((vBR.x - vTL.x) * (vTL.y - vBL.y) - (vBR.y - vTL.y) * (vTL.x - vBL.x)) * rd; + + olc::vf2d center; + if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) + center = vTL + rn * (vBR - vTL); + + std::array d = { { + (vTL - center).mag(), + (vTR - center).mag(), + (vBR - center).mag(), + (vBL - center).mag(), + } }; + + std::array q = { { + d[0] == 0.0f ? 1.0f : (d[0] + d[2]) / d[2], + d[1] == 0.0f ? 1.0f : (d[1] + d[3]) / d[3], + d[2] == 0.0f ? 1.0f : (d[2] + d[0]) / d[0], + d[3] == 0.0f ? 1.0f : (d[3] + d[1]) / d[1], + } }; + + return vecGPUTasks.emplace_back( + TaskTexturedPolygon( + GPUTask::Structure::Fan, + transformAffine.forward({ vTL, vTR, vBR, vBL }), + { {q[0], 1.0f}, {q[1], 1.0f}, {q[2], 1.0f}, {q[3], 1.0f} }, + { tint, tint, tint, tint}, + { image.coords[0] * q[0], image.coords[1] * q[1], image.coords[2] * q[2], image.coords[3] * q[3] }, + &image.image + )); + } + + // Default is just return a textured quad + return vecGPUTasks.emplace_back( + TaskTexturedPolygon( + GPUTask::Structure::Fan, + transformAffine.forward({ vTL, vTR, vBR, vBL }), + { tint, tint, tint, tint }, + { image.coords[0], image.coords[1], image.coords[2], image.coords[3] }, + &image.image + )); +} + +const GPUTask& olc::Draw2D::ImageQuad(olc::ImageRegion image, const std::vector& vecPoints, const olc::Pixel tint) +{ + return ImageQuad(image, vecPoints[0], vecPoints[1], vecPoints[2], vecPoints[3], tint); +} + + +const GPUTask& olc::Draw2D::ImageRect(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) +{ + // Ensure source image is up to date in VRAM + PrepareImageForHW(image.image); + + PrepareTargetForHW(); + + return vecGPUTasks.emplace_back( + TaskTexturedPolygon( + GPUTask::Structure::Fan, + transformAffine.forward({ { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }), + { tint, tint, tint, tint }, + // Tex coords are clockwise + { image.coords[3], image.coords[2], image.coords[1], image.coords[0] }, + &image.image + )); +} + +#define PGE_DRAW2D_IMPLEMENTED 1 +#endif + +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_CORE_IMPLEMENTED) +namespace olc +{ + bool PGEWindow::Create(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) + { + //pRenderer->RetargetDevice(pHost->GetHostWindowDescriptor(this)); + pRenderer->PrepareWindowTarget(pHost->GetHostWindowDescriptor(this)); + CreateImage(GetDefaultImage(), vScreenSize); + SetWindowSize(vScreenSize * vPixelSize); + return true; + } + + bool PGEWindow::OnUserCreate() + { + return true; + } + + bool PGEWindow::OnUserUpdate(float fElapsedTime) + { + return true; + } + + bool PGEWindow::OnUserDestroy() + { + return true; + } + + bool PGEWindow::olc_WindowUpdate(const float fElapsedTime) + { + // Input Changes + mouse.UpdateState(); + + draw.SetGPU(pRenderer); + draw.SetTarget(GetDefaultImage()); +#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES + pRenderer->RetargetDevice(pHost->GetHostWindowDescriptor(this)); +#endif + pRenderer->ApplyDefaultShader(); + + + + // User Update + if (!OnUserUpdate(fElapsedTime) || bRequestToClose) + { + // User has requested termination of window by returning false + if (OnUserDestroy()) + { + // User has confirmed window destruction by returning true + bShouldRemove = true; + } + else + bRequestToClose = false; // User vetoed closure + } + + + // Finialise any outstanding tasks + draw.ProcessGPUTasks(); + + // Take the window's completed "screen" and draw it as a textured quad to the backbuffer + pRenderer->AssignTextureTarget(0, 0); + pRenderer->SetViewport({ 0,0 }, GetWindowSize()); + pRenderer->ClearViewport(olc::Colour::MAGENTA, true, true); + + draw.WorldReset(); + draw.ImageRect(imgPrimary, { 0.0,0.0 }, GetWindowSize()); + draw.ProcessGPUTasks(); + + // Update Window's primary surface + //pRenderer->RetargetDevice(pHost->GetHostWindowDescriptor(this)); + pRenderer->DisplayDraw(pHost->GetHostWindowDescriptor(this)); + + return true; + } + + bool PGEWindow::CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg) + { + // Create CPU Image + if (!image.Create(size, cfg)) + return false; + + // Create GPU Image + auto id = pRenderer->CreateTexture(image.Size(), cfg); + if (id == 0) + { + image.Create({ 0,0 }); + return false; + } + + // Associate CPU object with GPU Resource + image.SetGPUID(id); + return true; + } + + bool PGEWindow::CreateImageFromFile(olc::Image& image, const std::string& sFileName, const ImageConfig& cfg) { + if (pImageLoader->CreateImageFromFile(image, sFileName)) + { + // Image has loaded ok, and populated into pixel vector + // + // Create GPU Image + auto id = pRenderer->CreateTexture(image.Size(), cfg); + if (id == 0) + { + image.Create({ 0,0 }); + return false; + } + + // Associate CPU object with GPU Resource + image.SetGPUID(id); + return true; + } + return false; } - //bool PixelGameEngine::WriteImageToMemory(const olc::Image& image, std::vector bytes, const std::string& sFileName) - //{ - // return false; - //} + bool PGEWindow::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes, const ImageConfig& cfg) + { + return false; + } - void PixelGameEngine::DestroyImage(olc::Image& image) + bool PGEWindow::WriteImageToFile(const olc::Image& image, const std::string& sFileName) + { + return false; + } + + void PGEWindow::DestroyImage(olc::Image& image) { // If image has gpu resource, remove it if (image.GetGPUID() != 0) { - gpu->DeleteTexture(image.GetGPUID()); + pRenderer->DeleteTexture(image.GetGPUID()); image.SetGPUID(0); } @@ -3909,26 +4819,141 @@ namespace olc image.Create({ 0,0 }); } - bool PixelGameEngine::olc_PrimaryWindowInit() + void PGEWindow::LinkToRenderer(olc::gpu::Renderer* gpu) + { + pRenderer = gpu; + } + + void PGEWindow::LinkToImageLoader(olc::imload::ImageLoader* imload) { - // Called by window to initialise core systems. Sub windows should ignore this + pImageLoader = imload; + } + + olc::Image& PGEWindow::GetDefaultImage() + { + return imgPrimary; + } + + olc::Draw2D& PGEWindow::GetDraw() + { + return draw; + } + + olc::hw::Mouse& PGEWindow::GetMouse() + { + return mouse; + } + + bool PGEWindow::olc_OnMouseMove(const olc::vi2d& vMousePos) + { + mouse.SetPosition(olc::vf2d(vMousePos) / olc::vf2d(GetWindowSize()) * GetDefaultImage().Size()); + return true; + } + + + + + + + PixelGameEngine::PixelGameEngine() : PGEWindow() + { + } + + PixelGameEngine::~PixelGameEngine() + { + } + + bool PixelGameEngine::Construct(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize, bool bFullScreen) + { + config.vScreenSize = vScreenSize; + config.vPixelSize = vPixelSize; + config.bFullScreen = bFullScreen; + + return true; + } + + bool PixelGameEngine::Construct(const PGEConfig& cfg) + { + config = cfg; + return true; + } + + bool PixelGameEngine::Start() + { + // Initialise Host Interface + host = std::make_unique(); + +#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_NO + // Create OS window on this thread + host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); + // Create EngineThread - no more windows will be created now. We needed one window + // at least to initialise teh rendering subsystem... sigh. + coreActive = true; + coreThread = std::thread(&PixelGameEngine::EngineThread, this); + // Handle window events on this thread (and block) + host->StartSystemEventLoop(true); + // Window has closed its event handler, so shut down gracefully + coreActive = false; + // Wait for engine thread to terminate + coreThread.join(); +#else + +#endif + + return true; + } + + bool PixelGameEngine::AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) + { +#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES + // Link this olc::Window to a host resource + if (host) + { + host->AddWindowFrame(window.get(), { 30,30 }, vScreenSize * vPixelSize, false); + window->LinkToHost(host.get()); + window->LinkToRenderer(gpu.get()); + window->LinkToImageLoader(imageloader.get()); + + //gpu->RetargetDevice(host->GetHostWindowDescriptor(window.get())); + window->Create(vScreenSize, vPixelSize); + deqChildWindows.push_back(window); + } + return true; +#else + // Can't create new windows return false; +#endif } + + void PixelGameEngine::EngineThread() { using namespace std::chrono_literals; timeFrame2 = std::chrono::steady_clock::now(); timeFrame1 = std::chrono::steady_clock::now(); +#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES + // Create Primary Window on EngineThread, event loop also exists for all windows + // on this thread, and all windows will be created on this thread + host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); +#endif + // Initialise ImageLoader Interface imageloader = std::make_unique(); // Initialise GPU Interface - This thread is the context olc::gpu::RendererConfig cfgRenderer; + cfgRenderer.VerticalSync = config.bVSync; + gpu = std::make_unique(); + // Link this windows devices + LinkToHost(host.get()); + LinkToRenderer(gpu.get()); + LinkToImageLoader(imageloader.get()); + // The GPU device can be based upon the primary window configuration. This // gives us completed gpu and host objects to pass to other windows as and // when required @@ -3941,24 +4966,44 @@ namespace olc } - CreateImage(imgPrimary, config.vScreenSize); - gpu->WriteTexture(imgPrimary.GetGPUID(), imgPrimary); + + + CreateImage(GetDefaultImage(), config.vScreenSize); + + + // Initialise Font System + olc::pgeguts::CreateClassicFont(this); + + + draw.SetGPU(gpu.get()); + gpu->ApplyDefaultShader(); + draw.SetTarget(GetDefaultImage()); if (!OnUserCreate()) { // Creation process signalled abort return; } - + + draw.ProcessGPUTasks(); + draw.SetTarget(GetDefaultImage()); + // Initialise Input Devices durationFrameCount = 0s; + + while (coreActive) { +#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES + // Multiwindow system uses one event loop (non blocking) for all windows + host->StartSystemEventLoop(false); +#endif + // Frame Delta Timing - "ElapsedTime" since last core update // ~~~~~~~~~~~~~~~~~~ // All timing is synchronous to the primary window, i.e child @@ -3986,42 +5031,52 @@ namespace olc { durationFrameCount -= 1s; std::string sTitle = "OneLoneCoder.com - Pixel Game Engine 3 - Test - FPS: " + std::to_string(frameCount); - SetTitle(sTitle); + SetWindowTitle(sTitle); frameCount = 0; } - - // Update Child Windows (if any) - for (auto& winChild : deqChildWindows) - winChild->olc_WindowUpdate(fDT, gpu.get()); + - // Update Primary Window - olc_WindowUpdate(fDT, gpu.get()); - // Wait for vertical sync if required. - // Note: Child windows will never vsync as waiting for each buffer swap with vsync - // divides up the frame rate budget across the windows. - if (gpu->GetConfig().VerticalSync) - { - host->SyncWithDesktopComposite(); - } - - // Remove child windows that have requested closure - if (!deqChildWindows.empty()) - { - deqChildWindows.erase(std::remove_if(deqChildWindows.begin(), deqChildWindows.end(), - [](const std::shared_ptr& w) - { - return w->olc_ShouldRemove(); - } - )); - } - // Primary Window if (olc_ShouldRemove()) { - // Application is to be terminated + // Application is to be terminated as primary window has closed coreActive = false; } + else + { +#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES + // Update Child Windows (if any) + for (auto& winChild : deqChildWindows) + winChild->olc_WindowUpdate(fDT); + + // Remove child windows that have requested closure + if (!deqChildWindows.empty()) + { + deqChildWindows.erase(std::remove_if(deqChildWindows.begin(), deqChildWindows.end(), + [this](const std::shared_ptr& w) + { + if (w->olc_ShouldRemove()) + { + host->CloseWindowFrame(w.get()); + } + return w->olc_ShouldRemove(); + } + ), deqChildWindows.end()); + } +#endif + + // Update Primary Window + olc_WindowUpdate(fDT); + + // Wait for vertical sync if required. + // Note: Child windows will never vsync as waiting for each buffer swap with vsync + // divides up the frame rate budget across the windows. + if (gpu->GetConfig().VerticalSync) + { + host->SyncWithDesktopComposite(); + } + } } @@ -4030,7 +5085,7 @@ namespace olc #define PGE_CORE_IMPLEMENTED 1 #endif -#if defined(OLC_PGE_APPLICATION) && !defined(PGE_IMAGE_IMPLEMENTED) +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_IMAGE_IMPLEMENTED) namespace olc { /*Image::Image(const olc::vi2d& size, const ImageConfig& cfg) @@ -4045,7 +5100,7 @@ namespace olc { dimensions = size; config = cfg; - pixels.resize(dimensions.area(), olc::Pixel(255, 165, 0)); + pixels.resize(dimensions.area(), olc::Colour::TANGERINE); return true; } @@ -4059,7 +5114,7 @@ namespace olc return pixels.data(); } - olc::Pixel& Image::Pixel(const olc::vf2d& pos) + olc::Pixel& Image::Pixel(const olc::vi2d& pos) { return pixels[int(pos.y) * dimensions.x + int(pos.x)]; } @@ -4084,6 +5139,13 @@ namespace olc return pixels; } + void Image::Resize(const olc::vi2d& size) + { + dimensions = size; + pixels.resize(dimensions.area(), olc::Colour::TANGERINE); + BindCPU(); + } + bool Image::BoundToGPU() const { return onGPU; @@ -4094,6 +5156,18 @@ namespace olc return onCPU; } + olc::ImageRegion Image::region(const olc::vf2d pos, const olc::vf2d& size) + { + return region(pos, { pos.x + size.x, pos.y }, { pos.x, pos.y + size.y }, pos + size); + } + + olc::ImageRegion Image::region(const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBL, const olc::vf2d& vBR) + { + auto i = 1.0f / this->Size(); + //return olc::ImageRegion(*this, vTL * i, vTR * i, vBL * i, vBR * i ); + return olc::ImageRegion(*this, (vTL + olc::vf2d{0.005f, 0.005f}) * i, (vTR + olc::vf2d{ -0.005f, 0.005f })* i, (vBL + olc::vf2d{ 0.005f, -0.005f })* i, (vBR + olc::vf2d{ -0.005f, -0.005f })* i); + } + void Image::BindGPU() { onGPU = true; @@ -4110,7 +5184,99 @@ namespace olc #define PGE_IMAGE_IMPLEMENTED 1 #endif -#if defined(OLC_PGE_APPLICATION) && !defined(PGE_HW_MOUSE_IMPLEMENTED) +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_FONT_IMPLEMENTED) +namespace olc +{ + namespace pgeguts + { + void CreateClassicFont(olc::PGEWindow* pge) + { + std::string data = + "?Q`0001oOch0o01o@F40o000000000" + "O000000nOT0063Qo4d8>?7a14Gno94AA4gno94AaOT0>o3`oO400o7QN00000400" + "Of80001oOg<7O7moBGT7O7lABET024@aBEd714AiOdl717a_=TH013Q>00000000" + "720D000V?V5oB3Q_HdUoE7a9@DdDE4A9@DmoE4A;Hg]oM4Aj8S4D84@`00000000" + "OaPT1000Oa`^13P1@AI[?g`1@A=[OdAoHgljA4Ao?WlBA7l1710007l100000000" + "ObM6000oOfMV?3QoBDD`O7a0BDDH@5A0BDD<@5A0BGeVO5ao@CQR?5Po00000000" + "Oc``000?Ogij70PO2D]??0Ph2DUM@7i`2DTg@7lh2GUj?0TO0C1870T?00000000" + "70<4001o?P<7?1QoHg43O;`h@GT0@:@LB@d0>:@hN@L0@?aoN@<0O7ao0000?000" + "OcH0001SOglLA7mg24TnK7ln24US>0PL24U140PnOgl0>7QgOcH0K71S0000A000" + "00H00000@Dm1S007@DUSg00?OdTnH7YhOfTL<7Yh@Cl0700?@Ah0300700000000" + "<008001QL00ZA41a@6HnI<1i@FHLM81M@@0LG81?O`0nC?Y7?`0ZA7Y300080000" + "O`082000Oh0827mo6>Hn?Wmo?6HnMb11MP08@C11H`08@FP0@@0004@000000000" + "00P00001Oab00003OcKP0006@6=PMgl<@440MglH@000000`@000001P00000000" + "Ob@8@@00Ob@8@Ga13R@8Mga172@8?PAo3R@827QoOb@820@0O`0007`0000007P0" + "O`000P08Od400g`<3V=P0G`673IP0`@3>1`00P@6O`P00g` vSpacing = { + 0x03,0x25,0x16,0x08,0x07,0x08,0x08,0x04,0x15,0x15,0x08,0x07,0x15,0x07,0x24,0x08, + 0x08,0x17,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x24,0x15,0x06,0x07,0x16,0x17, + 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x17,0x08,0x08,0x17,0x08,0x08,0x08, + 0x08,0x08,0x08,0x08,0x17,0x08,0x08,0x08,0x08,0x17,0x08,0x15,0x08,0x15,0x08,0x08, + 0x24,0x18,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x33,0x17,0x17,0x33,0x18,0x17,0x17, + 0x17,0x17,0x17,0x17,0x07,0x17,0x17,0x18,0x18,0x17,0x17,0x07,0x33,0x07,0x08,0x00 }; + + + pge->CreateImage(fontClassicPGE.imgFont, { 128, 48 }); + + fontClassicPGE.imgFont.BindCPU(); + + int px = 0, py = 0; + for (size_t b = 0; b < 1024; b += 4) + { + uint32_t sym1 = (uint32_t)data[b + 0] - 48; + uint32_t sym2 = (uint32_t)data[b + 1] - 48; + uint32_t sym3 = (uint32_t)data[b + 2] - 48; + uint32_t sym4 = (uint32_t)data[b + 3] - 48; + uint32_t r = sym1 << 18 | sym2 << 12 | sym3 << 6 | sym4; + + for (int i = 0; i < 24; i++) + { + int k = r & (1 << i) ? 255 : 0; + fontClassicPGE.imgFont.Pixel({ px, py }) = olc::Pixel(k, k, k, k); + if (++py == 48) { px++; py = 0; } + } + } + + for (int c = 0; c < 256; c++) + { + if (c >= 32 && c < 128) + { + int i = c - 32; + + float fSpace = float(vSpacing[i] >> 4); + float fWidth = float(vSpacing[i] & 15); + + olc::vf2d vPos = + { float((i % 16) * 8) + fSpace, float((i / 16) * 8) }; + + olc::vf2d vSize = + { fWidth, 8.0f }; + + FontGlyph glyph{ + fontClassicPGE.imgFont.region(vPos, vSize), + fSpace, + vSize, + { 8.0f, 8.0f }, + }; + fontClassicPGE.glyphs.push_back(glyph); + } + else + fontClassicPGE.glyphs.push_back(FontGlyph{ fontClassicPGE.imgFont.region({0,0}, {8,8}) , 8.0f, {8.0f, 8.0f} }); + } + + fontClassicPGE.fLineHeight = 10.0f; + fontClassicPGE.fTabWidth = 32.0f; + } + } +} +#define PGE_FONT_IMPLEMENTED 1 +#endif + +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HW_MOUSE_IMPLEMENTED) namespace olc::hw { const Button& Mouse::GetButton(const int nButton) const @@ -4123,6 +5289,11 @@ namespace olc::hw return position; } + int32_t Mouse::GetWheel() const + { + return wheel; + } + void Mouse::SetPosition(const olc::vf2d& pos) { position_in = pos; @@ -4133,6 +5304,11 @@ namespace olc::hw buttons_new[nButton] = state; } + void Mouse::SetWheel(const int32_t w) + { + wheel_in = w; + } + void Mouse::UpdateState() { for (size_t i=0; iUpdateWindowFrameTitle(this); } - bool Window::OnUserCreate() - { - return false; - } - - bool Window::OnUserUpdate(float fElapsedTime) - { - return false; - } - - bool Window::OnUserDestroy() - { - return false; - } bool Window::olc_OnMouseButton(const uint8_t nButton, const bool bPressed) { @@ -4203,13 +5367,14 @@ namespace olc bool Window::olc_OnMouseMove(const olc::vi2d& vMousePos) { - mouse.SetPosition(olc::vf2d(vMousePos) / olc::vf2d(GetSize()) * imgPrimary.Size()); + mouse.SetPosition(olc::vf2d(vMousePos) / olc::vf2d(GetWindowSize())); return true; } bool Window::olc_OnMouseWheel(const int32_t nScroll) { - return false; + mouse.SetWheel(nScroll); + return true; } bool Window::olc_OnMouseFocus(const bool bHasFocus) @@ -4224,80 +5389,37 @@ namespace olc bool Window::olc_OnWindowSize(const olc::vi2d& vWindowSize) { - return SetSize(vWindowSize); + return SetWindowSize(vWindowSize); } - bool Window::olc_ShouldRemove() const + bool Window::olc_OnWindowClose() { - return bShouldRemove; + bRequestToClose = true; + return true; } - bool Window::olc_WindowUpdate(const float fElapsedTime, olc::gpu::Renderer* const gpu) + bool Window::olc_ShouldRemove() const { - // Environmental changes - - // Input Changes - mouse.UpdateState(); - - - draw.SetGPU(gpu); - draw.SetTarget(imgPrimary); - - - gpu->DisplayPrepare(); - - - gpu->AssignTextureTarget(0, imgPrimary.GetGPUID()); - gpu->SetViewport({ 0,0 }, imgPrimary.Size()); - gpu->ClearViewport(olc::Colour::BLACK, true, true); - - gpu->ApplyDefaultShader(); - - // User Update - if (!OnUserUpdate(fElapsedTime)) - { - // User has requested termination of window by returning false - if (OnUserDestroy()) - { - // User has confirmed window destruction by returning true - bShouldRemove = true; - } - } - - - // Finialise any outstanding tasks - draw.ProcessGPUTasks(); - - // Take the window's completed "screen" and draw it as a textured quad to the backbuffer - gpu->AssignTextureTarget(0, 0); - gpu->SetViewport({ 0,0 }, GetSize()); - gpu->ClearViewport(olc::Colour::BLANK, true, true); - draw.ClearTransform(); - draw.Image(imgPrimary, { -1.0,1.0 }, { 2.0f,-2.0f }); - draw.ProcessGPUTasks(); - - // Update Window's primary surface - gpu->DisplayDraw(); - - return true; + return bShouldRemove; } + size_t Window::GetUID() const { return nUniqueID; } - const olc::vi2d& Window::GetSize() const + const olc::vi2d& Window::GetWindowSize() const { return vWindowSize; } - const olc::vi2d& Window::GetPosition() const + const olc::vi2d& Window::GetWindowPosition() const { return vWindowPos; } - bool Window::SetSize(const olc::vi2d& vSize) + bool Window::SetWindowSize(const olc::vi2d& vSize) { // Request host to change geometry of window object @@ -4305,7 +5427,7 @@ namespace olc return true; } - bool Window::SetPosition(const olc::vi2d& vPosition) + bool Window::SetWindowPosition(const olc::vi2d& vPosition) { // Request host to change geometry of window object @@ -4313,27 +5435,23 @@ namespace olc return true; } - const std::string& Window::GetTitle() const + const std::string& Window::GetWindowTitle() const { return sFrameTitle; } - bool Window::SetTitle(const std::string& sTitle) + bool Window::SetWindowTitle(const std::string& sTitle) { sFrameTitle = sTitle; pHost->UpdateWindowFrameTitle(this); return false; } - bool Window::olc_PrimaryWindowInit() - { - return false; - } }; #define PGE_WINDOW_IMPLEMENTED 1 #endif -#if defined(OLC_PGE_APPLICATION) && !defined(PGE_IMAGELOADER_IMPLEMENTED) +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_IMAGELOADER_IMPLEMENTED) #if OLC_IMAGELOADER == OLC_IMAGELOADER_WINGDI namespace olc::imload { diff --git a/dev/msvc/olcPGE3_BuildSH/test_sh.cpp b/dev/msvc/olcPGE3_BuildSH/test_sh.cpp index 2b7e2170..5c24cbb9 100644 --- a/dev/msvc/olcPGE3_BuildSH/test_sh.cpp +++ b/dev/msvc/olcPGE3_BuildSH/test_sh.cpp @@ -1,4 +1,4 @@ -#define OLC_PGE_APPLICATION +#define OLC_PGE3_APPLICATION #include "olcPixelGameEngine3.h" class Example : public olc::PixelGameEngine @@ -32,35 +32,35 @@ class Example : public olc::PixelGameEngine draw.SetTarget(imTest); - draw.FillRect({ 0,0 }, imTest.Size(), olc::Colour::BLUE); + draw.FilledRect({ 0,0 }, imTest.Size(), olc::Colour::BLUE); - draw.AffineRotate(fAngle, { 32.0f, 32.0f }); + draw.WorldRotate(fAngle, { 32.0f, 32.0f }); draw.Rect({ 5,5 }, { 10,10 }, olc::Colour::YELLOW); draw.Line({ 0,0 }, { 20, 20 }); - draw.SetTarget(imgPrimary); - draw.AffineReset(); + draw.SetTarget(GetDefaultImage()); + draw.WorldReset(); - draw.FillRect({ 0,0 }, imgPrimary.Size(), olc::Colour::VERY_DARK_MAGENTA); + draw.FilledRect({ 0,0 }, GetDefaultImage().Size(), olc::Colour::VERY_DARK_MAGENTA); - draw.Line({ 0,0 }, imgPrimary.Size() - 1, olc::Colour::RED); - draw.Line(olc::vf2d(imgPrimary.Size().x - 1, 0), olc::vf2d(0, imgPrimary.Size().y - 1), olc::Colour::GREEN); + draw.Line({ 0,0 }, GetDefaultImage().Size() - 1, olc::Colour::RED); + draw.Line(olc::vf2d(GetDefaultImage().Size().x - 1, 0), olc::vf2d(0, GetDefaultImage().Size().y - 1), olc::Colour::GREEN); if (mouse.GetButton(0).bHeld) draw.Line({ 0,0 }, mouse.GetPosition(), olc::Colour::TANGERINE); - draw.AffineRotate(fAngle * 0.2f, imgPrimary.Size() * 0.5f); - draw.Rect({ 0,0 }, imgPrimary.Size()); - draw.Image(imTest, { 10, 10 }, { 64, 64 }); - draw.Image(imTest, { 100, 10 }, { 64, 64 }); - draw.Image(imTest, { 10, 100 }, { 64, 64 }); - draw.Image(sprite1, { 100, 100 }, { 64, 64 }); + draw.WorldRotate(fAngle * 0.2f, GetDefaultImage().Size() * 0.5f); + draw.Rect({ 0,0 }, GetDefaultImage().Size()); + draw.Image(imTest, { 10, 10 }); + draw.Image(imTest, { 100, 10 }); + draw.Image(imTest, { 10, 100 }); + draw.Image(sprite1, { 100, 100 }, {0.1f, 0.1f}); olc::Pixel p = draw.GetPixel(imTest, { 8,5 }); - draw.FillRect({ 200,200 }, { 10,10 }, p); + draw.FilledRect({ 200,200 }, { 10,10 }, p); return true; } }; diff --git a/dev/src/api_opengl.cpp b/dev/src/api_opengl.cpp index 6d254174..a168a720 100644 --- a/dev/src/api_opengl.cpp +++ b/dev/src/api_opengl.cpp @@ -52,7 +52,7 @@ namespace olc::apis::opengl return bLoaded; } - bool gl::CheckError(const std::source_location loc) + bool gl::CheckError([[maybe_unused]] const std::source_location loc) { #if OLC_GPU_ERRORCHECK == 1 GLenum err; @@ -195,6 +195,12 @@ namespace olc::apis::opengl CheckError(); } + void gl::glHint(GLenum target, GLenum mode) + { + ::glHint(target, mode); + CheckError(); + } + GLuint gl::glCreateShader(GLenum type) { return _glCreateShader(type); diff --git a/dev/src/api_opengl.h b/dev/src/api_opengl.h index 13b36ea8..738637a9 100644 --- a/dev/src/api_opengl.h +++ b/dev/src/api_opengl.h @@ -213,7 +213,7 @@ namespace olc void glBlendFunc(GLenum sfactor, GLenum dfactor); void glDepthFunc(GLenum func); void glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels); - + void glHint(GLenum target, GLenum mode); private: bool CheckError(const std::source_location loc = std::source_location::current()); diff --git a/dev/src/config.h b/dev/src/config.h index 93ec4638..2522c90e 100644 --- a/dev/src/config.h +++ b/dev/src/config.h @@ -80,11 +80,22 @@ #endif -#define OLC_MOUSE_BUTTONS 3 +#define OLC_MULTIWINDOW_NO 1 +#define OLC_MULTIWINDOW_YES 2 + +#if !defined(OLC_MULTIWINDOW) + #define OLC_MULTIWINDOW OLC_MULTIWINDOW_NO +#endif + + +#define OLC_MOUSE_BUTTONS 5 #define OLC_GPU_MAX_VERTICES 8192 #define OLC_GPU_ERRORCHECK 0 #define LICENCE_DEFAULT "OneLoneCoder.com - Pixel Game Engine 3 - " +// De-Noise in MSVC (C++20) +#pragma warning(disable:4820) // Disable Padding Warnings + //! END CONFIGURATION \ No newline at end of file diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 33781124..f879abdd 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -7,75 +7,85 @@ //! START IMPLEMENTATION namespace olc { - PixelGameEngine::PixelGameEngine() : Window() + bool PGEWindow::Create(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) { + //pRenderer->RetargetDevice(pHost->GetHostWindowDescriptor(this)); + pRenderer->PrepareWindowTarget(pHost->GetHostWindowDescriptor(this)); + CreateImage(GetDefaultImage(), vScreenSize); + SetWindowSize(vScreenSize * vPixelSize); + return true; } - PixelGameEngine::~PixelGameEngine() + bool PGEWindow::OnUserCreate() { + return true; } - bool PixelGameEngine::Construct(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize, bool bFullScreen) + bool PGEWindow::OnUserUpdate(float fElapsedTime) { - config.vScreenSize = vScreenSize; - config.vPixelSize = vPixelSize; - config.bFullScreen = bFullScreen; - return true; } - bool PixelGameEngine::Construct(const PGEConfig& cfg) - { - config = cfg; + bool PGEWindow::OnUserDestroy() + { return true; } - bool PixelGameEngine::Start() + bool PGEWindow::olc_WindowUpdate(const float fElapsedTime) { - // Initialise Host Interface - host = std::make_unique(); + // Input Changes + mouse.UpdateState(); + draw.SetGPU(pRenderer); + draw.SetTarget(GetDefaultImage()); +#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES + pRenderer->RetargetDevice(pHost->GetHostWindowDescriptor(this)); +#endif + pRenderer->ApplyDefaultShader(); - // Link this olc::Window to a host resource - if (host) - host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); - - coreActive = true; - coreThread = std::thread(&PixelGameEngine::EngineThread, this); - // This will block depending upon the system - host->StartSystemEventLoop(); - // Gracefully terminate the main engine thread - coreActive = false; - coreThread.join(); + // User Update + if (!OnUserUpdate(fElapsedTime) || bRequestToClose) + { + // User has requested termination of window by returning false + if (OnUserDestroy()) + { + // User has confirmed window destruction by returning true + bShouldRemove = true; + } + else + bRequestToClose = false; // User vetoed closure + } - return true; - } - bool PixelGameEngine::OnUserCreate() - { - return false; - } + // Finialise any outstanding tasks + draw.ProcessGPUTasks(); - bool PixelGameEngine::OnUserUpdate(float fElapsedTime) - { - return false; - } + // Take the window's completed "screen" and draw it as a textured quad to the backbuffer + pRenderer->AssignTextureTarget(0, 0); + pRenderer->SetViewport({ 0,0 }, GetWindowSize()); + pRenderer->ClearViewport(olc::Colour::MAGENTA, true, true); + + draw.WorldReset(); + draw.ImageRect(imgPrimary, { 0.0,0.0 }, GetWindowSize()); + draw.ProcessGPUTasks(); - bool PixelGameEngine::OnUserDestroy() - { - return false; + // Update Window's primary surface + //pRenderer->RetargetDevice(pHost->GetHostWindowDescriptor(this)); + pRenderer->DisplayDraw(pHost->GetHostWindowDescriptor(this)); + + return true; } - bool PixelGameEngine::CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg) + bool PGEWindow::CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg) { // Create CPU Image if (!image.Create(size, cfg)) return false; // Create GPU Image - auto id = gpu->CreateTexture(image.Size(), cfg); + auto id = pRenderer->CreateTexture(image.Size(), cfg); if (id == 0) { image.Create({ 0,0 }); @@ -87,14 +97,14 @@ namespace olc return true; } - bool PixelGameEngine::CreateImageFromFile(olc::Image& image, const std::string& sFileName, const ImageConfig& cfg) - { - if (imageloader->CreateImageFromFile(image, sFileName)) + bool PGEWindow::CreateImageFromFile(olc::Image& image, const std::string& sFileName, const ImageConfig& cfg) + { + if (pImageLoader->CreateImageFromFile(image, sFileName)) { // Image has loaded ok, and populated into pixel vector // // Create GPU Image - auto id = gpu->CreateTexture(image.Size(), cfg); + auto id = pRenderer->CreateTexture(image.Size(), cfg); if (id == 0) { image.Create({ 0,0 }); @@ -109,27 +119,22 @@ namespace olc return false; } - bool PixelGameEngine::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes, const ImageConfig& cfg) + bool PGEWindow::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes, const ImageConfig& cfg) { return false; } - bool PixelGameEngine::WriteImageToFile(const olc::Image& image, const std::string& sFileName) + bool PGEWindow::WriteImageToFile(const olc::Image& image, const std::string& sFileName) { return false; } - //bool PixelGameEngine::WriteImageToMemory(const olc::Image& image, std::vector bytes, const std::string& sFileName) - //{ - // return false; - //} - - void PixelGameEngine::DestroyImage(olc::Image& image) + void PGEWindow::DestroyImage(olc::Image& image) { // If image has gpu resource, remove it if (image.GetGPUID() != 0) { - gpu->DeleteTexture(image.GetGPUID()); + pRenderer->DeleteTexture(image.GetGPUID()); image.SetGPUID(0); } @@ -137,18 +142,126 @@ namespace olc image.Create({ 0,0 }); } - bool PixelGameEngine::olc_PrimaryWindowInit() + void PGEWindow::LinkToRenderer(olc::gpu::Renderer* gpu) + { + pRenderer = gpu; + } + + void PGEWindow::LinkToImageLoader(olc::imload::ImageLoader* imload) + { + pImageLoader = imload; + } + + olc::Image& PGEWindow::GetDefaultImage() { - // Called by window to initialise core systems. Sub windows should ignore this + return imgPrimary; + } + + olc::Draw2D& PGEWindow::GetDraw() + { + return draw; + } + + olc::hw::Mouse& PGEWindow::GetMouse() + { + return mouse; + } + + bool PGEWindow::olc_OnMouseMove(const olc::vi2d& vMousePos) + { + mouse.SetPosition(olc::vf2d(vMousePos) / olc::vf2d(GetWindowSize()) * GetDefaultImage().Size()); + return true; + } + + + + + + + PixelGameEngine::PixelGameEngine() : PGEWindow() + { + } + + PixelGameEngine::~PixelGameEngine() + { + } + + bool PixelGameEngine::Construct(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize, bool bFullScreen) + { + config.vScreenSize = vScreenSize; + config.vPixelSize = vPixelSize; + config.bFullScreen = bFullScreen; + + return true; + } + + bool PixelGameEngine::Construct(const PGEConfig& cfg) + { + config = cfg; + return true; + } + + bool PixelGameEngine::Start() + { + // Initialise Host Interface + host = std::make_unique(); + +#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_NO + // Create OS window on this thread + host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); + // Create EngineThread - no more windows will be created now. We needed one window + // at least to initialise teh rendering subsystem... sigh. + coreActive = true; + coreThread = std::thread(&PixelGameEngine::EngineThread, this); + // Handle window events on this thread (and block) + host->StartSystemEventLoop(true); + // Window has closed its event handler, so shut down gracefully + coreActive = false; + // Wait for engine thread to terminate + coreThread.join(); +#else + +#endif + + return true; + } + + bool PixelGameEngine::AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) + { +#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES + // Link this olc::Window to a host resource + if (host) + { + host->AddWindowFrame(window.get(), { 30,30 }, vScreenSize * vPixelSize, false); + window->LinkToHost(host.get()); + window->LinkToRenderer(gpu.get()); + window->LinkToImageLoader(imageloader.get()); + + //gpu->RetargetDevice(host->GetHostWindowDescriptor(window.get())); + window->Create(vScreenSize, vPixelSize); + deqChildWindows.push_back(window); + } + return true; +#else + // Can't create new windows return false; +#endif } + + void PixelGameEngine::EngineThread() { using namespace std::chrono_literals; timeFrame2 = std::chrono::steady_clock::now(); timeFrame1 = std::chrono::steady_clock::now(); +#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES + // Create Primary Window on EngineThread, event loop also exists for all windows + // on this thread, and all windows will be created on this thread + host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); +#endif + // Initialise ImageLoader Interface imageloader = std::make_unique(); @@ -159,6 +272,11 @@ namespace olc gpu = std::make_unique(); + // Link this windows devices + LinkToHost(host.get()); + LinkToRenderer(gpu.get()); + LinkToImageLoader(imageloader.get()); + // The GPU device can be based upon the primary window configuration. This // gives us completed gpu and host objects to pass to other windows as and // when required @@ -171,29 +289,44 @@ namespace olc } - CreateImage(imgPrimary, config.vScreenSize); + CreateImage(GetDefaultImage(), config.vScreenSize); + + + // Initialise Font System + olc::pgeguts::CreateClassicFont(this); + + draw.SetGPU(gpu.get()); gpu->ApplyDefaultShader(); - draw.SetTarget(imgPrimary); + draw.SetTarget(GetDefaultImage()); if (!OnUserCreate()) { // Creation process signalled abort return; } + + draw.ProcessGPUTasks(); - draw.SetTarget(imgPrimary); + draw.SetTarget(GetDefaultImage()); // Initialise Input Devices durationFrameCount = 0s; + + while (coreActive) { +#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES + // Multiwindow system uses one event loop (non blocking) for all windows + host->StartSystemEventLoop(false); +#endif + // Frame Delta Timing - "ElapsedTime" since last core update // ~~~~~~~~~~~~~~~~~~ // All timing is synchronous to the primary window, i.e child @@ -221,42 +354,52 @@ namespace olc { durationFrameCount -= 1s; std::string sTitle = "OneLoneCoder.com - Pixel Game Engine 3 - Test - FPS: " + std::to_string(frameCount); - SetTitle(sTitle); + SetWindowTitle(sTitle); frameCount = 0; } - - // Update Child Windows (if any) - for (auto& winChild : deqChildWindows) - winChild->olc_WindowUpdate(fDT, gpu.get()); + - // Update Primary Window - olc_WindowUpdate(fDT, gpu.get()); - // Wait for vertical sync if required. - // Note: Child windows will never vsync as waiting for each buffer swap with vsync - // divides up the frame rate budget across the windows. - if (gpu->GetConfig().VerticalSync) - { - host->SyncWithDesktopComposite(); - } - - // Remove child windows that have requested closure - if (!deqChildWindows.empty()) - { - deqChildWindows.erase(std::remove_if(deqChildWindows.begin(), deqChildWindows.end(), - [](const std::shared_ptr& w) - { - return w->olc_ShouldRemove(); - } - )); - } - // Primary Window if (olc_ShouldRemove()) { - // Application is to be terminated + // Application is to be terminated as primary window has closed coreActive = false; } + else + { +#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES + // Update Child Windows (if any) + for (auto& winChild : deqChildWindows) + winChild->olc_WindowUpdate(fDT); + + // Remove child windows that have requested closure + if (!deqChildWindows.empty()) + { + deqChildWindows.erase(std::remove_if(deqChildWindows.begin(), deqChildWindows.end(), + [this](const std::shared_ptr& w) + { + if (w->olc_ShouldRemove()) + { + host->CloseWindowFrame(w.get()); + } + return w->olc_ShouldRemove(); + } + ), deqChildWindows.end()); + } +#endif + + // Update Primary Window + olc_WindowUpdate(fDT); + + // Wait for vertical sync if required. + // Note: Child windows will never vsync as waiting for each buffer swap with vsync + // divides up the frame rate budget across the windows. + if (gpu->GetConfig().VerticalSync) + { + host->SyncWithDesktopComposite(); + } + } } diff --git a/dev/src/core.h b/dev/src/core.h index 2bdc9b14..4a40b81b 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -20,6 +20,7 @@ #include "gpu_iface.h" #include "host_iface.h" #include "imload_iface.h" +#include "font.h" //! END CUSTOMHEADER GLOBAL //! START DECLARATION @@ -53,28 +54,20 @@ namespace olc bool bAllowChildWindows = true; }; - class PixelGameEngine : protected Window + class PGEWindow : public Window { public: - PixelGameEngine(); - virtual ~PixelGameEngine(); - + bool Create(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize); + public: - bool Construct(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize, bool bFullScreen = false); - bool Construct(const PGEConfig& cfg = PGEConfig{}); - - public: - bool Start(); - - public: // The "Overridables" - // Called once when the engine is ready and the window is created - bool OnUserCreate() override; - // Called every frame while the engine is running - bool OnUserUpdate(float fElapsedTime) override; - // Called when something requests the engine shut down - bool OnUserDestroy() override; - - + // Return true if window is to continue + virtual bool OnUserCreate(); + // Return true if window is to continue + virtual bool OnUserUpdate(float fElapsedTime); + // Return true if window is to close + virtual bool OnUserDestroy(); + + public: // olc::Image Handling // Create an image resource bool CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg = olc::ImageConfig()); @@ -89,14 +82,54 @@ namespace olc // Destroy an image void DestroyImage(olc::Image& image); + public: + void LinkToRenderer(olc::gpu::Renderer* gpu); + void LinkToImageLoader(olc::imload::ImageLoader* imload); + + + public: + olc::Image& GetDefaultImage(); + olc::Draw2D& GetDraw(); + olc::hw::Mouse& GetMouse(); + + protected: - bool olc_PrimaryWindowInit() override; + bool olc_OnMouseMove(const olc::vi2d& vMousePos) override; + + public: + virtual bool olc_WindowUpdate(const float fElapsedTime); + + protected: + olc::Draw2D draw; + + + private: + olc::Image imgPrimary; + olc::gpu::Renderer* pRenderer = nullptr; + olc::imload::ImageLoader* pImageLoader = nullptr; + }; + + class PixelGameEngine : public PGEWindow + { + public: + PixelGameEngine(); + virtual ~PixelGameEngine(); + + public: + bool Construct(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize, bool bFullScreen = false); + bool Construct(const PGEConfig& cfg = PGEConfig{}); + + public: + bool Start(); + + public: // Child Windows + bool AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize); private: void EngineThread(); private: - std::deque> deqChildWindows; + std::deque> deqChildWindows; // Frame Timing & Overall Clocking std::chrono::steady_clock::time_point timeFrame1; diff --git a/dev/src/draw2d.cpp b/dev/src/draw2d.cpp index d639ab34..286bceea 100644 --- a/dev/src/draw2d.cpp +++ b/dev/src/draw2d.cpp @@ -25,38 +25,14 @@ void Draw2D::SetTarget(olc::Image& image) // Store the target image pTarget = ℑ - // Create a transform to denormalise the image and present it - // in pixel space - transformTarget.scale(olc::vf2d(2.0f, 2.0f) / olc::vf2d(pTarget->Size())); - transformTarget.translate( - olc::vf2d( - -1.0f,// +(1.0f / float(pTarget->Size().x)), - -1.0f// + (1.0f / float(pTarget->Size().y)) - )); - + // Reset Affine transform to unity WorldReset(); + // Configure default render target pRenderer->AssignTextureTarget(0, pTarget->GetGPUID()); pRenderer->SetViewport({ 0,0 }, pTarget->Size()); } -void Draw2D::ClearTransform() -{ - transformTarget = olc::tf2d(); - transformAffine = olc::tf2d(); - transformCombined = olc::mf3d(); -} - -void Draw2D::SetTransform(const olc::tf2d& trans) -{ - transformTarget = trans; -} - -tf2d& olc::Draw2D::GetTransform() -{ - return transformAffine; -} - void olc::Draw2D::ProcessGPUTasks() { for (const auto& task : vecGPUTasks) @@ -114,6 +90,22 @@ void Draw2D::PrepareImageForHW(olc::Image& image) // Image resource is primed for CPU operations, send it to GPU pRenderer->WriteTexture(image.GetGPUID(), image); + if (&image == pTarget) + { + // If this image is also the current target, ensure renderer is updated + //SetTarget(image); + + // Store the target image + pTarget = ℑ + + // Reset Affine transform to unity + WorldReset(); + + // Configure default render target + pRenderer->AssignTextureTarget(0, pTarget->GetGPUID()); + pRenderer->SetViewport({ 0,0 }, pTarget->Size()); + } + // Image is now GPU bound image.BindGPU(); } @@ -122,25 +114,31 @@ void Draw2D::PrepareImageForHW(olc::Image& image) void olc::Draw2D::WorldReset() { transformAffine = olc::tf2d(); - transformCombined = transformAffine * transformTarget; } void olc::Draw2D::WorldScale(const olc::vf2d& vScale) { transformAffine.scale(vScale); - transformCombined = transformAffine * transformTarget; } void olc::Draw2D::WorldOffset(const olc::vf2d& vOffset) { transformAffine.translate(vOffset); - transformCombined = transformAffine * transformTarget; } void olc::Draw2D::WorldRotate(const float& fTheta, const olc::vf2d& vPoint) { transformAffine.rotate(fTheta, vPoint); - transformCombined = transformAffine * transformTarget; +} + +void olc::Draw2D::SetWorldTransform(const olc::tf2d& trans) +{ + transformAffine = trans; +} + +olc::tf2d& olc::Draw2D::GetWorldTransform() +{ + return transformAffine; } olc::vf2d olc::Draw2D::WorldToScreen(const olc::vf2d& v) const @@ -203,7 +201,12 @@ GPUTask olc::Draw2D::TaskDrawPolygon(GPUTask::Structure structure, const std::ve GPUTask olc::Draw2D::TaskFillPolygon(GPUTask::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) { - return GPUTask(); + GPUTask task; + task.structure = structure; + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer.push_back({ vPoints[i].x, vPoints[i].y, 1.0f, 1.0f, vColours[i], 0, 0, 0, 0, 0, 0, 0, 0 }); + task.tint = tint; + return task; } GPUTask olc::Draw2D::TaskFillPolygon(GPUTask::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) @@ -237,32 +240,33 @@ GPUTask olc::Draw2D::TaskTexturedPolygon(GPUTask::Structure structure, const std return task; } -GPUTask Draw2D::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) + + +const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) { PrepareTargetForHW(); return vecGPUTasks.emplace_back( TaskDrawPolygon( GPUTask::Structure::Line, - //transformCombined.transform({ p1, p2 }), - {p1, p2}, + transformAffine.forward({p1, p2}), col, olc::Colour::WHITE )); } -GPUTask Draw2D::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel c1, const olc::Pixel c2) +const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel c1, const olc::Pixel c2) { PrepareTargetForHW(); return vecGPUTasks.emplace_back( TaskDrawPolygon( GPUTask::Structure::Line, - transformCombined.transform({ p1, p2 }), + transformAffine.forward({ p1, p2 }), { c1, c2 }, olc::Colour::WHITE - )); + )); } -GPUTask olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) { // Right a big, hearty, F&^% you to OpenGL's Diamond Exit Strategy. It makes line drawing // with OpenGL a smidge unreliable @@ -280,40 +284,148 @@ GPUTask olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc transformAffine.forward({ olc::vf2d( pos.x + 0.0f, pos.y + 0.0f ), olc::vf2d( pos.x + size.x +0.0f, pos.y + 0.0f ), - - //olc::vf2d( pos.x + size.x + 0.0f, pos.y + 0.0f), olc::vf2d( pos.x + size.x + 0.0f, pos.y + size.y + 0.0f ), - - //olc::vf2d( pos.x + size.x + 0.0f, pos.y + size.y + 0.0f), olc::vf2d( pos.x + 0.0f, pos.y + size.y + 0.0f ), - - //olc::vf2d( pos.x + 0.0f, pos.y + size.y + 0.0f), - //olc::vf2d( pos.x + 0.0f, pos.y + 0.0f), }), - - // transformCombined.transform({ { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }), col, olc::Colour::WHITE )); +} +const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +{ + PrepareTargetForHW(); + return vecGPUTasks.emplace_back( + TaskDrawPolygon( + GPUTask::Structure::Fan, + transformAffine.forward({ + olc::vf2d(pos.x + 0.0f, pos.y + 0.0f), + olc::vf2d(pos.x + size.x + 0.0f, pos.y + 0.0f), + olc::vf2d(pos.x + size.x + 0.0f, pos.y + size.y + 0.0f), + olc::vf2d(pos.x + 0.0f, pos.y + size.y + 0.0f), + }), + { colTL, colTR, colBR, colBL }, + olc::Colour::WHITE + )); } -GPUTask olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) { PrepareTargetForHW(); return vecGPUTasks.emplace_back( TaskFillPolygon( GPUTask::Structure::Fan, - //transformCombined.transform({ { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }), - { { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }, + transformAffine.forward({ { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }), col, olc::Colour::WHITE )); } -GPUTask olc::Draw2D::Image(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale) +const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +{ + PrepareTargetForHW(); + return vecGPUTasks.emplace_back( + TaskFillPolygon( + GPUTask::Structure::Fan, + transformAffine.forward({ { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }), + { colTL, colTR, colBR, colBL }, + olc::Colour::WHITE + )); +} + +const GPUTask& olc::Draw2D::String(const olc::vf2d& pos, const std::string& text, const olc::Pixel col, const olc::vf2d& scale, olc::Font& font) +{ + PrepareTargetForHW(); + + olc::vf2d spos = { 0.0f, 0.0f }; + + for (auto c : text) + { + const auto& glyph = font.glyphs[c]; + + if (c == '\n') + { + spos.x = 0; + spos.y += font.fLineHeight * scale.y; + } + else if (c == '\t') + { + spos.x += font.fTabWidth * scale.x; + } + else + { + Draw2D::Image(font.glyphs[c].imgGlyph, pos + spos, scale, col); + spos.x += glyph.vMonoSize.x * scale.x; + } + } + + return vecGPUTasks.emplace_back(); +} + +const GPUTask& olc::Draw2D::StringProp(const olc::vf2d& pos, const std::string& text, const olc::Pixel col, const olc::vf2d& scale, olc::Font& font) +{ + PrepareTargetForHW(); + + olc::vf2d spos = { 0.0f, 0.0f }; + + for (auto c : text) + { + const auto& glyph = font.glyphs[c]; + + if (c == '\n') + { + spos.x = 0; + spos.y += font.fLineHeight * scale.y; + } + else if (c == '\t') + { + spos.x += font.fTabWidth * scale.x; + } + else + { + Draw2D::Image(font.glyphs[c].imgGlyph, pos + spos, scale, col); + spos.x += glyph.vPropSize.x * scale.x; + } + } + + return vecGPUTasks.emplace_back(); +} + +olc::vf2d olc::Draw2D::GetTextSize(const std::string& text, const bool bProportional, const olc::vf2d& scale, olc::Font& font) +{ + olc::vf2d size = { 0, font.fLineHeight * scale.y }; + olc::vf2d pos = { 0, font.fLineHeight * scale.y }; + + for (auto c : text) + { + const auto& glyph = font.glyphs[c]; + + if (c == '\n') + { + pos.y += font.fLineHeight * scale.y; + pos.x = 0; + } + else if (c == '\t') + { + pos.x += font.fTabWidth * scale.x; + } + else + { + if(bProportional) + pos.x += glyph.vPropSize.x * scale.x; + else + pos.x += glyph.vMonoSize.x * scale.x; + } + + size = size.max(pos); + } + + return size; +} + +const GPUTask& olc::Draw2D::Image(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) { // Ensure source image is up to date in VRAM PrepareImageForHW(image.image); @@ -325,16 +437,15 @@ GPUTask olc::Draw2D::Image(olc::ImageRegion image, const olc::vf2d& pos, const o return vecGPUTasks.emplace_back( TaskTexturedPolygon( GPUTask::Structure::Fan, - //transformCombined.transform({ { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }), - { { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }, - { olc::Colour::WHITE, olc::Colour::WHITE, olc::Colour::WHITE, olc::Colour::WHITE }, + transformAffine.forwardRound({ { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }), + { tint, tint, tint, tint }, // Tex coords are clockwise { image.coords[0], image.coords[1], image.coords[2], image.coords[3]}, &image.image )); } -GPUTask olc::Draw2D::ImageRotated(olc::ImageRegion image, const olc::vf2d& pos, const float theta, const olc::vf2d& center, const olc::vf2d& scale) +const GPUTask& olc::Draw2D::ImageRotated(olc::ImageRegion image, const olc::vf2d& pos, const float theta, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel tint) { // Ensure source image is up to date in VRAM PrepareImageForHW(image.image); @@ -355,15 +466,14 @@ GPUTask olc::Draw2D::ImageRotated(olc::ImageRegion image, const olc::vf2d& pos, return vecGPUTasks.emplace_back( TaskTexturedPolygon( GPUTask::Structure::Fan, - //transformCombined.transform(vPoints), - vPoints, - { olc::Colour::WHITE, olc::Colour::WHITE, olc::Colour::WHITE, olc::Colour::WHITE }, + transformAffine.forward(vPoints), + { tint, tint, tint, tint}, { image.coords[0], image.coords[1], image.coords[2], image.coords[3] }, &image.image )); } -GPUTask olc::Draw2D::ImageQuad(olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, const olc::vf2d& vBL) +const GPUTask& olc::Draw2D::ImageQuad(olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, const olc::vf2d& vBL, const olc::Pixel tint) { // Ensure source image is up to date in VRAM PrepareImageForHW(image.image); @@ -398,25 +508,32 @@ GPUTask olc::Draw2D::ImageQuad(olc::ImageRegion image, const olc::vf2d& vTL, con return vecGPUTasks.emplace_back( TaskTexturedPolygon( GPUTask::Structure::Fan, - //transformCombined.transform({ vTL, vTR, vBR, vBL }), - { vTL, vTR, vBR, vBL }, + transformAffine.forward({ vTL, vTR, vBR, vBL }), { {q[0], 1.0f}, {q[1], 1.0f}, {q[2], 1.0f}, {q[3], 1.0f} }, - { olc::Colour::WHITE, olc::Colour::WHITE, olc::Colour::WHITE, olc::Colour::WHITE }, + { tint, tint, tint, tint}, { image.coords[0] * q[0], image.coords[1] * q[1], image.coords[2] * q[2], image.coords[3] * q[3] }, &image.image )); } - return GPUTask(); + // Default is just return a textured quad + return vecGPUTasks.emplace_back( + TaskTexturedPolygon( + GPUTask::Structure::Fan, + transformAffine.forward({ vTL, vTR, vBR, vBL }), + { tint, tint, tint, tint }, + { image.coords[0], image.coords[1], image.coords[2], image.coords[3] }, + &image.image + )); } -GPUTask olc::Draw2D::ImageQuad(olc::ImageRegion image, const std::vector& vecPoints) +const GPUTask& olc::Draw2D::ImageQuad(olc::ImageRegion image, const std::vector& vecPoints, const olc::Pixel tint) { - return ImageQuad(image, vecPoints[0], vecPoints[1], vecPoints[2], vecPoints[3]); + return ImageQuad(image, vecPoints[0], vecPoints[1], vecPoints[2], vecPoints[3], tint); } -GPUTask olc::Draw2D::ImageRect(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size) +const GPUTask& olc::Draw2D::ImageRect(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) { // Ensure source image is up to date in VRAM PrepareImageForHW(image.image); @@ -426,9 +543,8 @@ GPUTask olc::Draw2D::ImageRect(olc::ImageRegion image, const olc::vf2d& pos, con return vecGPUTasks.emplace_back( TaskTexturedPolygon( GPUTask::Structure::Fan, - //transformCombined.transform({ { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }), - { { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }, - { olc::Colour::WHITE, olc::Colour::WHITE, olc::Colour::WHITE, olc::Colour::WHITE }, + transformAffine.forward({ { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }), + { tint, tint, tint, tint }, // Tex coords are clockwise { image.coords[3], image.coords[2], image.coords[1], image.coords[0] }, &image.image diff --git a/dev/src/draw2d.h b/dev/src/draw2d.h index b549381c..a2a70a00 100644 --- a/dev/src/draw2d.h +++ b/dev/src/draw2d.h @@ -16,6 +16,7 @@ #include "pixel.h" #include "image.h" #include "gputask.h" +#include "font.h" //! END CUSTOMHEADER //! START DECLARATION @@ -35,19 +36,13 @@ namespace olc // Associate this drawing toolbox with a renderer void SetGPU(olc::gpu::Renderer* const renderer); + void ProcessGPUTasks(); public: // Sets the drawing target of this drawing toolbox void SetTarget(olc::Image& image); - // Clears transform stack and sets identity transform - void ClearTransform(); - - void SetTransform(const olc::tf2d& trans); - olc::tf2d& GetTransform(); - - void ProcessGPUTasks(); @@ -56,10 +51,12 @@ namespace olc void WorldScale(const olc::vf2d& vScale); void WorldOffset(const olc::vf2d& vOffset); void WorldRotate(const float& fTheta, const olc::vf2d& vPoint = { 0,0 }); + void SetWorldTransform(const olc::tf2d& trans); + olc::tf2d& GetWorldTransform(); olc::vf2d WorldToScreen(const olc::vf2d& v) const; olc::vf2d ScreenToWorld(const olc::vf2d& v) const; - public: + public: // Primitive Drawing Functions // Plot a single pixel void Pixel( const olc::vf2d& pos, @@ -70,36 +67,54 @@ namespace olc olc::Image& image, const olc::vf2d& pos); - // Clear + // Clear entire draw target to specific colour void Clear(const olc::Pixel& col); - public: + public: // Shape Drawing Functions // Draws a single pixel wide line - GPUTask Line( + const GPUTask& Line( const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col = olc::Colour::WHITE); // Draws a single pixel wide line with a gradient - GPUTask Line( + const GPUTask& Line( const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel c1, const olc::Pixel c2); // Draws a rectangle outline - GPUTask Rect( + const GPUTask& Rect( const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::Colour::WHITE); + // Draws a multiple colour rectangle, with linear colour interpolation + const GPUTask& Rect( + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel colTL, + const olc::Pixel colTR, + const olc::Pixel colBL, + const olc::Pixel colBR); + // Draws a filled, single colour rectangle - GPUTask FilledRect( + const GPUTask& FilledRect( const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col = olc::Colour::WHITE); - //GPUTask ShadedRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR); + // Draws a filled, multiple colour rectangle, with linear colour interpolation + const GPUTask& FilledRect( + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel colTL, + const olc::Pixel colTR, + const olc::Pixel colBL, + const olc::Pixel colBR); + + //TexturedRect @@ -128,36 +143,67 @@ namespace olc //ShadedPolygon //TexturedPolygon - + public: // Text Drawing Functions + // Draws a string at specified location in monospace font + const GPUTask& String( + const olc::vf2d& pos, + const std::string& text, + const olc::Pixel col = olc::Colour::WHITE, + const olc::vf2d& scale = { 1.0f, 1.0f }, + olc::Font& font = olc::fontClassicPGE); + + // Draws a string at specified location in proportional font + const GPUTask& StringProp( + const olc::vf2d& pos, + const std::string& text, + const olc::Pixel col = olc::Colour::WHITE, + const olc::vf2d& scale = { 1.0f, 1.0f }, + olc::Font& font = olc::fontClassicPGE); + + olc::vf2d GetTextSize( + const std::string& text, + const bool bProportional = false, + const olc::vf2d& scale = { 1.0f, 1.0f }, + olc::Font& font = olc::fontClassicPGE); + + public: // Image Drawing Functions // Draws a scaled image at specified location - GPUTask Image( + const GPUTask& Image( olc::ImageRegion image, const olc::vf2d& pos, - const olc::vf2d& scale = { 1.0f, 1.0f }); + const olc::vf2d& scale = { 1.0f, 1.0f }, + const olc::Pixel tint = olc::Colour::WHITE); // Draws an image rotated around a point at specified location - GPUTask ImageRotated( + const GPUTask& ImageRotated( olc::ImageRegion image, const olc::vf2d& pos, const float theta, const olc::vf2d& center = { 0.0f, 0.0f }, - const olc::vf2d& scale = { 1.0f, 1.0f }); + const olc::vf2d& scale = { 1.0f, 1.0f }, + const olc::Pixel tint = olc::Colour::WHITE); - // Draws an image warped correctly to linearly fill a quadrilateral (formerly DrawWarped...) - GPUTask ImageQuad( + // Draws an image warped correctly to linearly fill a quadrilateral (formerly DrawWarped...) + const GPUTask& ImageQuad( olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, - const olc::vf2d& vBL); + const olc::vf2d& vBL, + const olc::Pixel tint = olc::Colour::WHITE); - GPUTask ImageQuad( + // Draws an image warped correctly to linearly fill a quadrilateral (formerly DrawWarped...) + const GPUTask& ImageQuad( olc::ImageRegion image, - const std::vector& vecPoints); - + const std::vector& vecPoints, + const olc::Pixel tint = olc::Colour::WHITE); // Draws an image scaled to a specified rectangular area - GPUTask ImageRect(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size); + const GPUTask& ImageRect( + olc::ImageRegion image, + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel tint = olc::Colour::WHITE); @@ -204,6 +250,52 @@ namespace olc olc::Image* const image, const olc::Pixel tint = olc::Colour::WHITE); + + public: // Precision drawing functions via software rasteriser + // Draws a single pixel wide line of fixed colour + void swLine( + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::Pixel col = olc::Colour::WHITE); + + // Draws a single pixel wide line with a gradient + void swLine( + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::Pixel c1, + const olc::Pixel c2); + + // Draws a rectangle outline + void swRect( + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel col = olc::Colour::WHITE); + + // Draws a multiple colour rectangle, with linear colour interpolation + void swRect( + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel colTL, + const olc::Pixel colTR, + const olc::Pixel colBL, + const olc::Pixel colBR); + + + + // Software rasteriser helper functions + + // Clips a line to a rectangular region, returns true if line is visible + bool swClipLine( + olc::vf2d& v0, + olc::vf2d& v1, + const olc::vf2d& vMin, + const olc::vf2d& vMax); + + + + + + protected: // Checks residency of image resource, and brings it to cpu RAM for r/w void PrepareTargetForSW(); @@ -217,9 +309,7 @@ namespace olc olc::Image* pTarget = nullptr; olc::gpu::Renderer* pRenderer = nullptr; - olc::tf2d transformTarget; olc::tf2d transformAffine; - olc::mf3d transformCombined; std::vector vecGPUTasks; diff --git a/dev/src/draw2d_sw.cpp b/dev/src/draw2d_sw.cpp new file mode 100644 index 00000000..7a342598 --- /dev/null +++ b/dev/src/draw2d_sw.cpp @@ -0,0 +1,236 @@ +#include "draw2d.h" + +#include "gpu_iface.h" + + + +//! START IMPLEMENTATION + +// This is all essentially the olc::PixelGameEngine 2 rasteriser code +using namespace olc; + + +void Draw2D::swLine(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) +{ + swLine(p1, p2, col, col); +} + +void Draw2D::swLine(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel c1, const olc::Pixel c2) +{ + PrepareTargetForSW(); + + // Lambda to draw a pixel gated by a pattern bit + uint32_t pattern = 0xFFFFFFFF; + auto rol = [&](void) + { + pattern = (pattern << 1) | (pattern >> 31); + return pattern & 1; + }; + + // Lambda to draw a pixel at integer location + auto Plot = [&](int32_t x, int32_t y, const olc::Pixel& p) + { + if (x >= 0 && x < pTarget->Size().x && y >= 0 && y < pTarget->Size().y) + pTarget->Pixel({ x, y }) = p; + }; + + // Clip line to draw target + olc::vf2d clipped_p1 = transformAffine.forward(p1); + olc::vf2d clipped_p2 = transformAffine.forward(p2); + + // If line is completely outside bounds, exit + if (!swClipLine(clipped_p1, clipped_p2, { 0,0 }, pTarget->Size())) + return; + + // Move to integer space + olc::vi2d ip1 = clipped_p1.floor(); + olc::vi2d ip2 = clipped_p2.floor(); + olc::vi2d pixel; + + // Gradients + olc::vi2d diff1 = ip2 - ip1; + + // Colour interpolation variables + float fColourT = 0.0f; + float fColourStep = 1.0f / float(std::max(std::abs(diff1.x), std::abs(diff1.y))); + + // Quick draw straight lines + if (diff1.x == 0) // Line is vertical + { + if (ip2.y < ip1.y) + { + std::swap(ip1.y, ip2.y); + fColourStep *= -1.0f; + fColourT = 1.0f; + } + + for (pixel.y = ip1.y; pixel.y <= ip2.y; pixel.y++) + { + if (rol()) + Plot(ip1.x, pixel.y, olc::PixelLerp(c1, c2, fColourT)); + + fColourT += fColourStep; + } + + // Early exit + return; + } + + if (diff1.y == 0) // Line is horizontal + { + if (ip2.x < ip1.x) + { + std::swap(ip1.x, ip2.x); + fColourStep *= -1.0f; + fColourT = 1.0f; + } + + + for (pixel.x = ip1.x; pixel.x <= ip2.x; pixel.x++) + { + if (rol()) + Plot(pixel.x, ip1.y, olc::PixelLerp(c1, c2, fColourT)); + + fColourT += fColourStep; + } + + // Early exit + return; + } + + // Line is sloped + olc::vi2d diff2 = diff1.abs(); + + // Apply Bresenham algorithm + olc::vi2d p = { diff2.y - diff2.x * 2, diff2.x - diff2.y * 2 }; + olc::vi2d end = { 0,0 }; + + if (diff2.y <= diff2.x) // Propoagate in x-direction + { + if (diff1.x >= 0) + { + pixel = ip1; + end = ip2; + } + else + { + pixel = ip2; + end = ip1; + fColourStep *= -1.0f; + fColourT = 1.0f; + } + + if (rol()) + Plot(pixel.x, pixel.y, olc::PixelLerp(c1, c2, fColourT)); + + for (int i = 0; pixel.x < end.x; i++) + { + pixel.x = pixel.x + 1; + fColourT += fColourStep; + + if (p.x < 0) + p.x = p.x + 2 * diff2.y; + else + { + if ((diff1.x < 0 && diff1.y < 0) || (diff1.x > 0 && diff1.y > 0)) + pixel.y = pixel.y + 1; + else + pixel.y = pixel.y - 1; + + p.x = p.x + 2 * (diff2.y - diff2.x); + } + + if (rol()) + Plot(pixel.x, pixel.y, olc::PixelLerp(c1, c2, fColourT)); + } + } + else + { + if (diff1.y >= 0) // Propogate in y-direction + { + pixel = ip1; + end = ip2; + } + else + { + pixel = ip2; + end = ip1; + fColourStep *= -1.0f; + fColourT = 1.0f; + } + + if (rol()) + Plot(pixel.x, pixel.y, olc::PixelLerp(c1, c2, fColourT)); + + for (int i = 0; pixel.y < end.y; i++) + { + pixel.y = pixel.y + 1; + fColourT += fColourStep; + + if (p.y <= 0) + p.y = p.y + 2 * diff2.x; + else + { + if ((diff1.x < 0 && diff1.y < 0) || (diff1.x > 0 && diff1.y > 0)) + pixel.x = pixel.x + 1; + else + pixel.x = pixel.x - 1; + + p.y = p.y + 2 * (diff2.x - diff2.y); + } + + if (rol()) + Plot(pixel.x, pixel.y, olc::PixelLerp(c1, c2, fColourT)); + } + } +} + +void olc::Draw2D::swRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +{ + swRect(pos, size, col, col, col, col); +} + +void olc::Draw2D::swRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +{ + swLine(pos, { pos.x + size.x, pos.y }, colTL, colTR); + swLine({ pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, colTR, colBR); + swLine({ pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y }, colBR, colBL); + swLine({ pos.x, pos.y + size.y }, pos, colBL, colTL); +} + +bool olc::Draw2D::swClipLine(olc::vf2d& p1, olc::vf2d& p2, const olc::vf2d& vMin, const olc::vf2d& vMax) +{ + // https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm + static constexpr int SEG_I = 0b0000, SEG_L = 0b0001, SEG_R = 0b0010, SEG_B = 0b0100, SEG_T = 0b1000; + auto Segment = [&vMin = vMin, &vMax = vMax](const olc::vi2d& v) + { + int i = SEG_I; + if (v.x < vMin.x) i |= SEG_L; else if (v.x > vMax.x) i |= SEG_R; + if (v.y < vMin.y) i |= SEG_B; else if (v.y > vMax.y) i |= SEG_T; + return i; + }; + + int s1 = Segment(p1), s2 = Segment(p2); + + while (true) + { + if (!(s1 | s2)) return true; + else if (s1 & s2) return false; + else + { + int s3 = s2 > s1 ? s2 : s1; + olc::vf2d n; + if (s3 & SEG_T) { n.x = p1.x + (p2.x - p1.x) * (vMax.y - p1.y) / (p2.y - p1.y); n.y = vMax.y; } + else if (s3 & SEG_B) { n.x = p1.x + (p2.x - p1.x) * (vMin.y - p1.y) / (p2.y - p1.y); n.y = vMin.y; } + else if (s3 & SEG_R) { n.x = vMax.x; n.y = p1.y + (p2.y - p1.y) * (vMax.x - p1.x) / (p2.x - p1.x); } + else if (s3 & SEG_L) { n.x = vMin.x; n.y = p1.y + (p2.y - p1.y) * (vMin.x - p1.x) / (p2.x - p1.x); } + if (s3 == s1) { p1 = n; s1 = Segment(p1); } + else { p2 = n; s2 = Segment(p2); } + } + } + return true; +} + + + +//! END IMPLEMENTATION \ No newline at end of file diff --git a/dev/src/font.cpp b/dev/src/font.cpp new file mode 100644 index 00000000..38074825 --- /dev/null +++ b/dev/src/font.cpp @@ -0,0 +1,94 @@ +#include "font.h" + +#include "core.h" + +//! START IMPLEMENTATION +namespace olc +{ + namespace pgeguts + { + void CreateClassicFont(olc::PGEWindow* pge) + { + std::string data = + "?Q`0001oOch0o01o@F40o000000000" + "O000000nOT0063Qo4d8>?7a14Gno94AA4gno94AaOT0>o3`oO400o7QN00000400" + "Of80001oOg<7O7moBGT7O7lABET024@aBEd714AiOdl717a_=TH013Q>00000000" + "720D000V?V5oB3Q_HdUoE7a9@DdDE4A9@DmoE4A;Hg]oM4Aj8S4D84@`00000000" + "OaPT1000Oa`^13P1@AI[?g`1@A=[OdAoHgljA4Ao?WlBA7l1710007l100000000" + "ObM6000oOfMV?3QoBDD`O7a0BDDH@5A0BDD<@5A0BGeVO5ao@CQR?5Po00000000" + "Oc``000?Ogij70PO2D]??0Ph2DUM@7i`2DTg@7lh2GUj?0TO0C1870T?00000000" + "70<4001o?P<7?1QoHg43O;`h@GT0@:@LB@d0>:@hN@L0@?aoN@<0O7ao0000?000" + "OcH0001SOglLA7mg24TnK7ln24US>0PL24U140PnOgl0>7QgOcH0K71S0000A000" + "00H00000@Dm1S007@DUSg00?OdTnH7YhOfTL<7Yh@Cl0700?@Ah0300700000000" + "<008001QL00ZA41a@6HnI<1i@FHLM81M@@0LG81?O`0nC?Y7?`0ZA7Y300080000" + "O`082000Oh0827mo6>Hn?Wmo?6HnMb11MP08@C11H`08@FP0@@0004@000000000" + "00P00001Oab00003OcKP0006@6=PMgl<@440MglH@000000`@000001P00000000" + "Ob@8@@00Ob@8@Ga13R@8Mga172@8?PAo3R@827QoOb@820@0O`0007`0000007P0" + "O`000P08Od400g`<3V=P0G`673IP0`@3>1`00P@6O`P00g` vSpacing = { + 0x03,0x25,0x16,0x08,0x07,0x08,0x08,0x04,0x15,0x15,0x08,0x07,0x15,0x07,0x24,0x08, + 0x08,0x17,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x24,0x15,0x06,0x07,0x16,0x17, + 0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x17,0x08,0x08,0x17,0x08,0x08,0x08, + 0x08,0x08,0x08,0x08,0x17,0x08,0x08,0x08,0x08,0x17,0x08,0x15,0x08,0x15,0x08,0x08, + 0x24,0x18,0x17,0x17,0x17,0x17,0x17,0x17,0x17,0x33,0x17,0x17,0x33,0x18,0x17,0x17, + 0x17,0x17,0x17,0x17,0x07,0x17,0x17,0x18,0x18,0x17,0x17,0x07,0x33,0x07,0x08,0x00 }; + + + pge->CreateImage(fontClassicPGE.imgFont, { 128, 48 }); + + fontClassicPGE.imgFont.BindCPU(); + + int px = 0, py = 0; + for (size_t b = 0; b < 1024; b += 4) + { + uint32_t sym1 = (uint32_t)data[b + 0] - 48; + uint32_t sym2 = (uint32_t)data[b + 1] - 48; + uint32_t sym3 = (uint32_t)data[b + 2] - 48; + uint32_t sym4 = (uint32_t)data[b + 3] - 48; + uint32_t r = sym1 << 18 | sym2 << 12 | sym3 << 6 | sym4; + + for (int i = 0; i < 24; i++) + { + int k = r & (1 << i) ? 255 : 0; + fontClassicPGE.imgFont.Pixel({ px, py }) = olc::Pixel(k, k, k, k); + if (++py == 48) { px++; py = 0; } + } + } + + for (int c = 0; c < 256; c++) + { + if (c >= 32 && c < 128) + { + int i = c - 32; + + float fSpace = float(vSpacing[i] >> 4); + float fWidth = float(vSpacing[i] & 15); + + olc::vf2d vPos = + { float((i % 16) * 8) + fSpace, float((i / 16) * 8) }; + + olc::vf2d vSize = + { fWidth, 8.0f }; + + FontGlyph glyph{ + fontClassicPGE.imgFont.region(vPos, vSize), + fSpace, + vSize, + { 8.0f, 8.0f }, + }; + fontClassicPGE.glyphs.push_back(glyph); + } + else + fontClassicPGE.glyphs.push_back(FontGlyph{ fontClassicPGE.imgFont.region({0,0}, {8,8}) , 8.0f, {8.0f, 8.0f} }); + } + + fontClassicPGE.fLineHeight = 10.0f; + fontClassicPGE.fTabWidth = 32.0f; + } + } +} +//! END IMPLEMENTATION diff --git a/dev/src/font.h b/dev/src/font.h new file mode 100644 index 00000000..5d16622c --- /dev/null +++ b/dev/src/font.h @@ -0,0 +1,70 @@ +#pragma once + +//! START STDHEADER GLOBAL +#include +#include +#include +#include +#include +#include +//! END STDHEADER + + +//! START CUSTOMHEADER GLOBAL +#include "config.h" +#include "image.h" +#include "imload_iface.h" +//! END CUSTOMHEADER + + +//! START DECLARATION +#if !defined(PGE_FONT_DECLARED) +namespace olc +{ + class PGEWindow; + + struct FontGlyph + { + // Region of an olc::Image that contains the glyph + // in bitmap format. Typically WHITE on TRANSPARENT... + // ...though have fun with crazy alternatives :P + olc::ImageRegion imgGlyph; + // Leading spacing in pixels before glyph + float spacing; + // Size of the glyph in pixels + olc::vf2d vPropSize; + // Size of the glyph in monospace format + olc::vf2d vMonoSize; + }; + + class Font + { + public: + Font() = default; + ~Font() = default; + + public: + // Image that contains all glyphs + olc::Image imgFont; + // All glyphs in the font + std::vector glyphs; + // Line height in pixels + float fLineHeight = 10.0f; + // Tab width in pixels + float fTabWidth = 32.0f; + }; + + // Predefined classic PGE font + inline olc::Font fontClassicPGE; + + namespace pgeguts + { + // Create the classic PGE font + void CreateClassicFont(olc::PGEWindow* pge); + } + +} + +#define PGE_FONT_DECLARED 1 +#endif +//! END DECLARATION \ No newline at end of file diff --git a/dev/src/gpu_iface.h b/dev/src/gpu_iface.h index edf28e45..13f49f23 100644 --- a/dev/src/gpu_iface.h +++ b/dev/src/gpu_iface.h @@ -76,9 +76,13 @@ namespace olc public: // Device Stuff // Constructs a GPU Device interface - virtual bool CreateDevice(std::vector params, const RendererConfig& cfg) = 0; + virtual bool CreateDevice(std::vector os_win_id, const RendererConfig& cfg) = 0; // Destroys a GPU device interface virtual bool DestroyDevice() = 0; + // If applicable, retarget the rendering context + virtual bool RetargetDevice(std::vector os_win_id) = 0; + // Prepare an OS rendering target + virtual bool PrepareWindowTarget(std::vector os_win_id) = 0; public: // Texture Resource Stuff // Allocates a new texture resource in VRAM, returns handle @@ -110,7 +114,7 @@ namespace olc // Configures defaults prior to drawing virtual bool DisplayPrepare() = 0; // Displays the final output - virtual bool DisplayDraw(bool bVerticalSyncNow = false) = 0; + virtual bool DisplayDraw(std::vector os_win_id, bool bVerticalSyncNow = false) = 0; protected: diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 52f20bd1..60fbdf8a 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -58,37 +58,22 @@ namespace olc::gpu - bool Renderer_OGL33::CreateDevice(std::vector params, const RendererConfig& cfg) + bool Renderer_OGL33::CreateDevice(std::vector os_win_id, const RendererConfig& cfg) { config = cfg; - // Create OpenGL Device Context + #if OLC_HOST == OLC_HOST_WINDOWS - // "wgl*" all live in WinGDI - glDeviceContext = GetDC((HWND)(params[0])); - - PIXELFORMATDESCRIPTOR pfd = - { - sizeof(PIXELFORMATDESCRIPTOR), 1, - PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, - PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - PFD_MAIN_PLANE, 0, 0, 0, 0 - }; - - int pf = 0; - if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) + // Create OpenGL Device Context + if (!PrepareWindowTarget(os_win_id)) { - lastError = RendererError::InvalidDCPixelFormat; return false; } - if (!SetPixelFormat(glDeviceContext, pf, &pfd)) - { - lastError = RendererError::FailedToSetDCPixelFormat; - return false; - } + auto glDeviceContext = GetDC((HWND)(os_win_id[0])); + // Create OpenGL Render Context if (!(glRenderContext = wglCreateContext(glDeviceContext))) { lastError = RendererError::FailedToCreateRenderContext; @@ -288,8 +273,9 @@ namespace olc::gpu // Unbind the FBO gl.glBindFramebuffer(36160U, 0); - glEnable(GL_TEXTURE_2D); // Turn on texturing - glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + gl.glEnable(GL_TEXTURE_2D); // Turn on texturing + gl.glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + gl.glEnable(GL_BLEND); lastError = RendererError::None; return true; @@ -305,6 +291,54 @@ namespace olc::gpu return false; } + bool Renderer_OGL33::RetargetDevice(std::vector os_win_id) + { +#if OLC_HOST == OLC_HOST_WINDOWS + auto glDeviceContext = GetDC((HWND)(os_win_id[0])); + + if (!wglMakeCurrent(glDeviceContext, glRenderContext)) + { + lastError = RendererError::FailedToSwitchRenderContext; + auto err = ::GetLastError(); + return false; + } + ReleaseDC((HWND)(os_win_id[0]), glDeviceContext); +#endif + + return true; + } + + bool Renderer_OGL33::PrepareWindowTarget(std::vector os_win_id) + { +#if OLC_HOST == OLC_HOST_WINDOWS + auto glDeviceContext = GetDC((HWND)(os_win_id[0])); + + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = 0; + if (!(pf = ChoosePixelFormat(glDeviceContext, &pfd))) + { + lastError = RendererError::InvalidDCPixelFormat; + return false; + } + + if (!SetPixelFormat(glDeviceContext, pf, &pfd)) + { + lastError = RendererError::FailedToSetDCPixelFormat; + return false; + } + ReleaseDC((HWND)(os_win_id[0]), glDeviceContext); +#endif + + return true; + } + uint32_t Renderer_OGL33::CreateTexture(const olc::vi2d& vSize, const olc::ImageConfig& cfg) { auto& gl = olc::apis::opengl::gl::Get(); @@ -343,6 +377,7 @@ namespace olc::gpu gl.glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); #endif + std::cout << "Created Texture ID: " << id << " Size: " << vSize.x << "x" << vSize.y << "\n"; return id; } @@ -374,8 +409,23 @@ namespace olc::gpu { auto& gl = olc::apis::opengl::gl::Get(); + // If the requested source texture is currently attached as the render target, + // unbind the framebuffer to avoid sampling from a texture that's being written to. + if (texid == nCurrentTextureTarget && texid != 0) + { +#if defined(OLC_GPU_ERRORCHECK) && OLC_GPU_ERRORCHECK == 1 + std::cout << "Warning ATS: Requested source is currently attached as target (" << texid << ") - unbinding FBO\n"; +#endif + gl.glBindFramebuffer(36160U, 0); + nCurrentTextureTarget = 0; + } + + //if (nCurrentTextureSource == texid) + // return true; + gl.glActiveTexture(0x84C0 + slot); // GL_TEXTURE0 gl.glBindTexture(GL_TEXTURE_2D, texid); + nCurrentTextureSource = texid; return true; } @@ -383,21 +433,50 @@ namespace olc::gpu { auto& gl = olc::apis::opengl::gl::Get(); + + // If the requested target texture is currently bound as a source, unbind it + // from all texture units to ensure we do not sample from a texture that's + // attached to the FBO (undefined behavior). + if (texid != 0 && texid == nCurrentTextureSource) + { +#if defined(OLC_GPU_ERRORCHECK) && OLC_GPU_ERRORCHECK == 1 + std::cout << "Warning ATT: Requested target is currently bound as source (" << texid << ") - unbinding texture units\n"; +#endif + // Unbind from a reasonable number of texture units (0..7) used by this renderer + for (int i = 0; i < 8; ++i) + { + gl.glActiveTexture(0x84C0 + i); + gl.glBindTexture(GL_TEXTURE_2D, 0); + } + // Reset to texture unit 0 + gl.glActiveTexture(0x84C0); + nCurrentTextureSource = 0; + } + if (texid == 0) { + // Unbind the FBO (bind default framebuffer) gl.glBindFramebuffer(36160U, 0); return true; - } - + } + // Bind FBO gl.glBindFramebuffer(36160U, nDefaultFBO); - // Allocate target buffers + + //gl.glEnable(GL_BLEND); + //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + // Allocate target buffers - pick the single attachment corresponding to 'slot' std::array attachments = { { 36064U, 36065U, 36066U, 36067U, 36068U, 36069U, 36070U, 36071U } }; - gl.glDrawBuffers(1, attachments.data()); - // Bind buffers to textures - gl.glFramebufferTexture2D(36160U, 36064U + slot, GL_TEXTURE_2D, texid, 0); + GLenum draw = attachments[slot]; + gl.glDrawBuffers(1, &draw); + // Bind buffers to texture + gl.glFramebufferTexture2D(36160U, 36064U + slot, GL_TEXTURE_2D, texid, 0); + + //glReadBuffer(36064U + slot); // GL_COLOR_ATTACHMENT0 + slot + + nCurrentTextureTarget = texid; return true; } @@ -482,6 +561,9 @@ namespace olc::gpu //if (task.bDepth) // gl.glEnable(GL_DEPTH_TEST); + gl.glEnable(GL_BLEND); + //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + gl.glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // Draw the thing! if (task.bWireframe) { @@ -545,16 +627,18 @@ namespace olc::gpu return false; } - bool Renderer_OGL33::DisplayDraw(bool bVerticalSyncNow) + bool Renderer_OGL33::DisplayDraw(std::vector os_win_id, bool bVerticalSyncNow) { auto& gl = olc::apis::opengl::gl::Get(); #if OLC_HOST == OLC_HOST_WINDOWS - SwapBuffers(glDeviceContext); + auto glDeviceContext = GetDC((HWND)(os_win_id[0])); + SwapBuffers(glDeviceContext); + ReleaseDC((HWND)(os_win_id[0]), glDeviceContext); #endif - return false; + return true; } diff --git a/dev/src/gpu_opengl33.h b/dev/src/gpu_opengl33.h index 175ab14e..cd29f201 100644 --- a/dev/src/gpu_opengl33.h +++ b/dev/src/gpu_opengl33.h @@ -20,9 +20,13 @@ namespace olc { public: // Device Stuff // Constructs a GPU Device interface - bool CreateDevice(std::vector params, const RendererConfig& cfg) override; + bool CreateDevice(std::vector os_win_id, const RendererConfig& cfg) override; // Destroys a GPU device interface bool DestroyDevice() override; + // If applicable, relocate the rendering context + bool RetargetDevice(std::vector os_win_id) override; + // Prepare an OS rendering target + bool PrepareWindowTarget(std::vector os_win_id) override; public: // Texture Resource Stuff @@ -54,11 +58,11 @@ namespace olc // Configures defaults prior to drawing virtual bool DisplayPrepare() override; // Displays the final output - virtual bool DisplayDraw(bool bVerticalSyncNow) override; + virtual bool DisplayDraw(std::vector os_win_id, bool bVerticalSyncNow) override; protected: // These may need some thinking about re multiple window - olc::apis::opengl::glDeviceContext_t glDeviceContext = 0; + //olc::apis::opengl::glDeviceContext_t glDeviceContext = 0; olc::apis::opengl::glRenderContext_t glRenderContext = 0; Shader_GLSL33 shaderDefault; @@ -68,6 +72,9 @@ namespace olc olc::Image imgBlank; olc::vf2d vTargetSize; + uint32_t nCurrentTextureTarget = 0; + uint32_t nCurrentTextureSource = 0; + }; } } diff --git a/dev/src/host_iface.h b/dev/src/host_iface.h index 52db6956..7d01094e 100644 --- a/dev/src/host_iface.h +++ b/dev/src/host_iface.h @@ -54,9 +54,9 @@ namespace olc HostError GetLastError() const { return lastError; } public: - virtual bool StartSystemEventLoop() = 0; + virtual bool StartSystemEventLoop(bool bBlockIfPossible = false) = 0; virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) = 0; - + virtual bool CloseWindowFrame(olc::Window* pWindow) = 0; virtual bool UpdateWindowFrameTitle(olc::Window* pWindow) = 0; virtual std::vector GetHostWindowDescriptor(olc::Window* pWindow) = 0; diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index faa444af..476a4a97 100644 --- a/dev/src/host_win_winapi.cpp +++ b/dev/src/host_win_winapi.cpp @@ -8,16 +8,28 @@ namespace olc::host // Windows app needs an event loop somewhere. This is blocking of course. This loop handles // all windows created for this host. - bool Host_Windows_WinAPI::StartSystemEventLoop() + bool Host_Windows_WinAPI::StartSystemEventLoop(bool bBlockIfPossible) { - MSG msg; - while (GetMessage(&msg, NULL, 0, 0) > 0) + if (bBlockIfPossible) { - TranslateMessage(&msg); - DispatchMessage(&msg); + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + else + { + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } } - return true; + return true; } // Static linkage to lpfnWndProc - the hWnd is tagged with meta-info to get @@ -73,7 +85,8 @@ namespace olc::host // Define WindowClass - WNDCLASS wc = { 0 }; + WNDCLASSEX wc = { 0 }; + wc.cbSize = sizeof(WNDCLASSEX); wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; @@ -84,7 +97,7 @@ namespace olc::host wc.lpszMenuName = nullptr; wc.hbrBackground = nullptr; wc.lpszClassName = olcT("OLC_PIXEL_GAME_ENGINE3"); - RegisterClass(&wc); + RegisterClassEx(&wc); // Define window furniture DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; @@ -110,13 +123,20 @@ namespace olc::host AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); int width = rWndRect.right - rWndRect.left; int height = rWndRect.bottom - rWndRect.top; - pWindow->SetSize(vWindowSize); + pWindow->SetWindowSize(vWindowSize); // Create the actual OS window, return a handle HWND hWnd = CreateWindowEx(dwExStyle, olcT("OLC_PIXEL_GAME_ENGINE3"), olcT(""), dwStyle, vTopLeft.x, vTopLeft.y, width, height, NULL, NULL, GetModuleHandle(nullptr), this); - SetWindowPos(hWnd, NULL, vWinPos.x, vWinPos.y, width, height, SWP_SHOWWINDOW); + LONG_PTR lp = GetWindowLongPtr(hWnd, GWL_STYLE); + SetWindowLongPtr(hWnd, GWL_STYLE, lp | (WS_CAPTION | WS_SYSMENU | WS_POPUPWINDOW | WS_THICKFRAME)); + lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | (WS_EX_WINDOWEDGE)); + + //SetWindowPos(hWnd, NULL, vWinPos.x, vWinPos.y, width, height, SWP_SHOWWINDOW); + //ShowWindow(hWnd, 1); + //UpdateWindow(hWnd); // Now... awkwardly, the above has already fired off some window messages // and they arent necessarily in a consistent order. Whereas one might @@ -130,7 +150,7 @@ namespace olc::host mapUID2HWND.insert_or_assign(pWindow->GetUID(), hWnd); mapHWND2PTR.insert_or_assign(hWnd, pWindow); - pWindow->ConnecToHost(this); + //DragAcceptFiles(olc_hWnd, true); @@ -138,12 +158,18 @@ namespace olc::host return true; } + bool Host_Windows_WinAPI::CloseWindowFrame(olc::Window* pWindow) + { + DestroyWindow((HWND)GetHostWindowDescriptor(pWindow).front()); + return false; + } + bool Host_Windows_WinAPI::UpdateWindowFrameTitle(olc::Window* pWindow) { #ifdef UNICODE - SetWindowText(mapUID2HWND.at(pWindow->GetUID()), ConvertS2W(pWindow->GetTitle()).c_str()); + SetWindowText(mapUID2HWND.at(pWindow->GetUID()), ConvertS2W(pWindow->GetWindowTitle()).c_str()); #else - SetWindowText(mapUID2HWND.at(pWindow->GetUID()), sTitle.c_str()); + SetWindowText(mapUID2HWND.at(pWindow->GetUID()), pWindow->GetWindowTitle().c_str()); #endif return true; } @@ -166,7 +192,7 @@ namespace olc::host LRESULT Host_Windows_WinAPI::OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (!mapHWND2PTR.contains(hWnd)) - return false; + return DefWindowProc(hWnd, uMsg, wParam, lParam);; // Get target olc::Window const auto& window = mapHWND2PTR.at(hWnd); @@ -185,19 +211,22 @@ namespace olc::host int16_t iy = *(int16_t*)&y; // Tell window new mouse location window->olc_OnMouseMove(olc::vi2d{ ix, iy }); - return 0; + break; } - break; // case WM_MOVE: vWinPos = olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); ptrPGE->olc_UpdateWindowPos(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF); return 0; case WM_SIZE: { window->olc_OnWindowSize(olc::vi2d(lParam & 0xFFFF, (lParam >> 16) & 0xFFFF)); - return 0; + break; + } + + case WM_MOUSEWHEEL: + { + window->olc_OnMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); + break; } - break; - // case WM_MOUSEWHEEL: ptrPGE->olc_UpdateMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); return 0; // case WM_MOUSELEAVE: ptrPGE->olc_UpdateMouseFocus(false); return 0; // case WM_SETFOCUS: ptrPGE->olc_UpdateKeyFocus(true); return 0; // case WM_KILLFOCUS: ptrPGE->olc_UpdateKeyFocus(false); return 0; @@ -208,32 +237,32 @@ namespace olc::host case WM_LBUTTONDOWN: { window->olc_OnMouseButton(0, true); - return 0; + break; } case WM_LBUTTONUP: { window->olc_OnMouseButton(0, false); - return 0; + break; } case WM_RBUTTONDOWN: { window->olc_OnMouseButton(1, true); - return 0; + break; } case WM_RBUTTONUP: { window->olc_OnMouseButton(1, false); - return 0; + break; } case WM_MBUTTONDOWN: { window->olc_OnMouseButton(2, true); - return 0; + break; } case WM_MBUTTONUP: { window->olc_OnMouseButton(2, false); - return 0; + break; } // case WM_DROPFILES: // { @@ -273,12 +302,19 @@ namespace olc::host // break; // // - // case WM_CLOSE: ptrPGE->olc_Terminate(); return 0; + case WM_CLOSE: + { + window->olc_OnWindowClose(); + break; + //return DefWindowProc(hWnd, uMsg, wParam, lParam); + } case WM_DESTROY: PostQuitMessage(0); DestroyWindow(hWnd); } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); } diff --git a/dev/src/host_win_winapi.h b/dev/src/host_win_winapi.h index eb6e94af..0da6a964 100644 --- a/dev/src/host_win_winapi.h +++ b/dev/src/host_win_winapi.h @@ -64,8 +64,9 @@ namespace olc public: - bool StartSystemEventLoop(); + bool StartSystemEventLoop(bool bBlockIfPossible = false); bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen); + bool CloseWindowFrame(olc::Window* pWindow); bool UpdateWindowFrameTitle(olc::Window* pWindow); std::vector GetHostWindowDescriptor(olc::Window* pWindow); diff --git a/dev/src/hw_mouse.cpp b/dev/src/hw_mouse.cpp index bf2af5c6..ea65e2b1 100644 --- a/dev/src/hw_mouse.cpp +++ b/dev/src/hw_mouse.cpp @@ -13,6 +13,11 @@ namespace olc::hw return position; } + int32_t Mouse::GetWheel() const + { + return wheel; + } + void Mouse::SetPosition(const olc::vf2d& pos) { position_in = pos; @@ -23,6 +28,11 @@ namespace olc::hw buttons_new[nButton] = state; } + void Mouse::SetWheel(const int32_t w) + { + wheel_in = w; + } + void Mouse::UpdateState() { for (size_t i=0; i buttons{}; @@ -37,10 +40,13 @@ namespace olc std::array buttons_old{}; olc::vf2d position; olc::vf2d position_in; + int32_t wheel_in; + int32_t wheel; private: void SetPosition(const olc::vf2d& pos); void SetButton(const int nButton, bool state); + void SetWheel(const int32_t w); void UpdateState(); }; } diff --git a/dev/src/image.cpp b/dev/src/image.cpp index f84d5766..a1102038 100644 --- a/dev/src/image.cpp +++ b/dev/src/image.cpp @@ -29,7 +29,7 @@ namespace olc return pixels.data(); } - olc::Pixel& Image::Pixel(const olc::vf2d& pos) + olc::Pixel& Image::Pixel(const olc::vi2d& pos) { return pixels[int(pos.y) * dimensions.x + int(pos.x)]; } @@ -54,6 +54,13 @@ namespace olc return pixels; } + void Image::Resize(const olc::vi2d& size) + { + dimensions = size; + pixels.resize(dimensions.area(), olc::Colour::TANGERINE); + BindCPU(); + } + bool Image::BoundToGPU() const { return onGPU; @@ -69,10 +76,11 @@ namespace olc return region(pos, { pos.x + size.x, pos.y }, { pos.x, pos.y + size.y }, pos + size); } - olc::ImageRegion Image::region(const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBL, const olc::vf2d& vBR) + olc::ImageRegion Image::region(const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBL, const olc::vf2d& vBR) { auto i = 1.0f / this->Size(); - return olc::ImageRegion(*this, vTL * i, vTR * i, vBL * i, vBR * i ); + //return olc::ImageRegion(*this, vTL * i, vTR * i, vBL * i, vBR * i ); + return olc::ImageRegion(*this, (vTL + olc::vf2d{0.005f, 0.005f}) * i, (vTR + olc::vf2d{ -0.005f, 0.005f })* i, (vBL + olc::vf2d{ 0.005f, -0.005f })* i, (vBR + olc::vf2d{ -0.005f, -0.005f })* i); } void Image::BindGPU() diff --git a/dev/src/image.h b/dev/src/image.h index d8b9300e..8d0f93e7 100644 --- a/dev/src/image.h +++ b/dev/src/image.h @@ -45,7 +45,7 @@ namespace olc // Returns read/write pointer to start of 1D stream of pixel data olc::Pixel* Data(); // [UNSAFE] Returns pixel at location - olc::Pixel& Pixel(const olc::vf2d& pos); + olc::Pixel& Pixel(const olc::vi2d& pos); // Returns how this image was configured upon creation const ImageConfig& GetConfig() const; // Return GPU Resource ID @@ -54,6 +54,8 @@ namespace olc void SetGPUID(const int32_t id); // Get underlying vector of pixels std::vector& GetPixels(); + + void Resize(const olc::vi2d& size); bool BoundToGPU() const; bool BoundToCPU() const; @@ -77,13 +79,6 @@ namespace olc struct ImageRegion { - ImageRegion(olc::Image& i, const olc::vf2d& vTL = { 0,0 }, const olc::vf2d& vTR = { 1,0 }, const olc::vf2d& vBL = { 0,1 }, const olc::vf2d& vBR = { 1,1 }) - : image(i) - { - coords = { vTL, vTR, vBR, vBL }; - regionsize = (vBR - vTL) * image.Size(); - } - olc::Image& image; olc::vf2d regionsize; union @@ -94,6 +89,18 @@ namespace olc olc::vf2d br; olc::vf2d bl; }; + + ImageRegion(olc::Image& i, const olc::vf2d& vTL = { 0,0 }, const olc::vf2d& vTR = { 1,0 }, const olc::vf2d& vBL = { 0,1 }, const olc::vf2d& vBR = { 1,1 }) + : image(i) + { + coords = { vTL, vTR, vBR, vBL }; + regionsize = (vBR - vTL) * image.Size(); + } + + ImageRegion& operator=(ImageRegion& o) + { + + } }; diff --git a/dev/src/imload_wingdi.h b/dev/src/imload_wingdi.h index 14dae709..a13da637 100644 --- a/dev/src/imload_wingdi.h +++ b/dev/src/imload_wingdi.h @@ -26,13 +26,13 @@ #endif // In Code::Blocks -#if !defined(_WIN32_WINNT) - #ifdef HAVE_MSMF - #define _WIN32_WINNT 0x0600 // Windows Vista - #else - #define _WIN32_WINNT 0x0500 // Windows 2000 - #endif -#endif +//#if !defined(_WIN32_WINNT) +// #ifdef HAVE_MSMF +// #define _WIN32_WINNT 0x0600 // Windows Vista +// #else +// #define _WIN32_WINNT 0x0500 // Windows 2000 +// #endif +//#endif // Embrace MSVC superiority #pragma comment(lib, "gdiplus.lib") diff --git a/dev/src/pixel.h b/dev/src/pixel.h index 4f92f10f..d8eeea93 100644 --- a/dev/src/pixel.h +++ b/dev/src/pixel.h @@ -72,7 +72,13 @@ namespace olc // Construction via individual channels inline constexpr Pixel(const uint8_t red, const uint8_t green, const uint8_t blue, const uint8_t alpha = 0xFF) - : a(alpha), b(blue), g(green), r(red) +#if PGE_PIXEL_LAYOUT == PGE_PIXEL_LAYOUT_RGBA + : r(red), g(green), b(blue), a(alpha) +#endif +#if PGE_PIXEL_LAYOUT == PGE_PIXEL_LAYOUT_ABGR + : a(alpha), b(blue), g(green), r(red) +#endif + { } // Construction via numeric assignment (e.g. #00DDBBFF) diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index 79e3c5cb..2455bcd4 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -1,12 +1,87 @@ #pragma once -// olc::PixelGameEngine 3.0 -// OneLoneCoder 2025 - // DO NOT EDIT THIS FILE! // This file is automatically generated. Changes to source should be made // through relevant component source file. +/* + olcPixelGameEngine3.h + + +-------------------------------------------------------------+ + | OneLoneCoder Pixel Game Engine v3.00 | + | "What do you need? Pixels... Lots of Pixels..." - javidx9 | + +-------------------------------------------------------------+ + + What is this? + ~~~~~~~~~~~~~ + olc::PixelGameEngine is a single file, cross platform graphics and userinput + framework used for games, visualisations, algorithm exploration and learning. + It was developed by YouTuber "javidx9" as an assistive tool for many of his + videos. The goal of this project is to provide high speed graphics with + minimal project setup complexity, to encourage new programmers, younger people, + and anyone else that wants to make fun things. + + However, olc::PixelGameEngine is not a toy! It is a powerful and fast utility + capable of delivering high resolution, high speed, high quality applications + which behave the same way regardless of the operating system or platform. + + This file provides the core utility set of the olc::PixelGameEngine, including + window creation, keyboard/mouse input, main game thread, timing, pixel drawing + routines, image/sprite loading and drawing routines, and a bunch of utility + types to make rapid development of games/visualisations possible. + + + License (OLC-3) + ~~~~~~~~~~~~~~~ + + Copyright 2018 - 2025 OneLoneCoder.com + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions or derivations of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions or derivative works in binary form must reproduce the above + copyright notice. This list of conditions and the following disclaimer must be + reproduced in the documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its contributors may + be used to endorse or promote products derived from this software without specific + prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT + SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + SUCH DAMAGE. + + TLDR: + You can do whatever you want with this code. Your only obligation is to credit in a + visible way to your end users in your running program that you used this code or a + derivative of it. If you don't edit this file, you are automatically in compliance with + OLC-3, as the message "OneLoneCoder.com" is present in the output window title bar. If + you compile to full screen, or remove the title bar, then you must display somewhere + something like "Powered by olcPixelGameEngine v3.00 - OneLoneCoder.com". That's it. + + Links + ~~~~~ + YouTube: https://www.youtube.com/javidx9 + https://www.youtube.com/javidx9extra + Discord: https://discord.gg/WhwHUMV + X: https://www.x.com/javidx9 + Twitch: https://www.twitch.tv/javidx9 + GitHub: https://www.github.com/onelonecoder + Homepage: https://www.onelonecoder.com + Patreon: https://www.patreon.com/javidx9 +*/ + + //! GRAB GLOBAL STDHEADER //! GRAB config.h CONFIGURATION @@ -21,6 +96,8 @@ //! GRAB image.h DECLARATION +//! GRAB font.h DECLARATION + //! GRAB imload_iface.h DECLARATION //! GRAB gputask.h DECLARATION @@ -46,6 +123,8 @@ + + #if OLC_HOST == OLC_HOST_WINDOWS //! GRAB host_win_winapi.h WINAPI_CONFIG @@ -73,14 +152,14 @@ -#if defined(OLC_PGE_APPLICATION) && !defined(PGE_HOST_IMPLEMENTED) +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HOST_IMPLEMENTED) #if OLC_HOST == OLC_HOST_WINDOWS //! GRAB host_win_winapi.cpp IMPLEMENTATION #endif #define PGE_HOST_IMPLEMENTED 1 #endif -#if defined(OLC_PGE_APPLICATION) && !defined(PGE_GPU_IMPLEMENTED) +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_GPU_IMPLEMENTED) #if OLC_GPU == OLC_GPU_OPENGL33 //! GRAB gpu_iface.cpp IMPLEMENTATION //! GRAB api_opengl.cpp IMPLEMENTATION @@ -89,32 +168,37 @@ #define PGE_GPU_IMPLEMENTED 1 #endif -#if defined(OLC_PGE_APPLICATION) && !defined(PGE_DRAW2D_IMPLEMENTED) +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_DRAW2D_IMPLEMENTED) //! GRAB draw2d.cpp IMPLEMENTATION #define PGE_DRAW2D_IMPLEMENTED 1 #endif -#if defined(OLC_PGE_APPLICATION) && !defined(PGE_CORE_IMPLEMENTED) +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_CORE_IMPLEMENTED) //! GRAB core.cpp IMPLEMENTATION #define PGE_CORE_IMPLEMENTED 1 #endif -#if defined(OLC_PGE_APPLICATION) && !defined(PGE_IMAGE_IMPLEMENTED) +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_IMAGE_IMPLEMENTED) //! GRAB image.cpp IMPLEMENTATION #define PGE_IMAGE_IMPLEMENTED 1 #endif -#if defined(OLC_PGE_APPLICATION) && !defined(PGE_HW_MOUSE_IMPLEMENTED) +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_FONT_IMPLEMENTED) +//! GRAB font.cpp IMPLEMENTATION +#define PGE_FONT_IMPLEMENTED 1 +#endif + +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HW_MOUSE_IMPLEMENTED) //! GRAB hw_mouse.cpp IMPLEMENTATION #define PGE_HW_MOUSE_IMPLEMENTED 1 #endif -#if defined(OLC_PGE_APPLICATION) && !defined(PGE_WINDOW_IMPLEMENTED) +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_WINDOW_IMPLEMENTED) //! GRAB window.cpp IMPLEMENTATION #define PGE_WINDOW_IMPLEMENTED 1 #endif -#if defined(OLC_PGE_APPLICATION) && !defined(PGE_IMAGELOADER_IMPLEMENTED) +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_IMAGELOADER_IMPLEMENTED) #if OLC_IMAGELOADER == OLC_IMAGELOADER_WINGDI //! GRAB imload_wingdi.cpp IMPLEMENTATION #endif diff --git a/dev/src/transform2d.h b/dev/src/transform2d.h index 0fcd5777..3f881d11 100644 --- a/dev/src/transform2d.h +++ b/dev/src/transform2d.h @@ -55,6 +55,12 @@ namespace olc return m_mForward * v; } + template + inline constexpr auto forwardRound(const olc::v_2d& v) const + { + return (m_mForward * v).round(); + } + // Transform a vector of v_2d by this transform template inline constexpr auto forward(const std::vector>& v) const @@ -64,6 +70,14 @@ namespace olc return o; } + template + inline constexpr auto forwardRound(const std::vector>& v) const + { + std::vector> o(v.size()); + std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return (m_mForward * i).round(); }); + return o; + } + // Transform a vector by the inverse of this transform template inline constexpr auto inverse(const olc::v_2d& v) const diff --git a/dev/src/vector2d.h b/dev/src/vector2d.h index 01f671c1..ac8be2db 100644 --- a/dev/src/vector2d.h +++ b/dev/src/vector2d.h @@ -95,6 +95,12 @@ namespace olc return v_2d(std::floor(x), std::floor(y)); } + // Rounds both components accurately + inline constexpr v_2d round() const + { + return v_2d(std::round(x), std::round(y)); + } + // Rounds both components up inline constexpr v_2d ceil() const { @@ -113,6 +119,12 @@ namespace olc return v_2d(std::min(x, v.x), std::min(y, v.y)); } + // Returns 'element-wise' abs of this vector + inline constexpr v_2d abs() const + { + return v_2d(std::abs(x), std::abs(y)); + } + // Calculates scalar dot product between this and another vector inline constexpr auto dot(const v_2d& rhs) const { diff --git a/dev/src/window.cpp b/dev/src/window.cpp index 35d17abd..f3e6772f 100644 --- a/dev/src/window.cpp +++ b/dev/src/window.cpp @@ -1,6 +1,8 @@ #include "window.h" #include "host_iface.h" +#include "gpu_iface.h" +#include "imload_iface.h" //! START IMPLEMENTATION namespace olc @@ -14,27 +16,13 @@ namespace olc { } - void Window::ConnecToHost(olc::host::Host* host) + void Window::LinkToHost(olc::host::Host* host) { pHost = host; sFrameTitle = "OneLoneCoder.com - Pixel Game Engine 3"; pHost->UpdateWindowFrameTitle(this); } - bool Window::OnUserCreate() - { - return false; - } - - bool Window::OnUserUpdate(float fElapsedTime) - { - return false; - } - - bool Window::OnUserDestroy() - { - return false; - } bool Window::olc_OnMouseButton(const uint8_t nButton, const bool bPressed) { @@ -44,13 +32,14 @@ namespace olc bool Window::olc_OnMouseMove(const olc::vi2d& vMousePos) { - mouse.SetPosition(olc::vf2d(vMousePos) / olc::vf2d(GetSize()) * imgPrimary.Size()); + mouse.SetPosition(olc::vf2d(vMousePos) / olc::vf2d(GetWindowSize())); return true; } bool Window::olc_OnMouseWheel(const int32_t nScroll) { - return false; + mouse.SetWheel(nScroll); + return true; } bool Window::olc_OnMouseFocus(const bool bHasFocus) @@ -65,81 +54,37 @@ namespace olc bool Window::olc_OnWindowSize(const olc::vi2d& vWindowSize) { - return SetSize(vWindowSize); + return SetWindowSize(vWindowSize); } - bool Window::olc_ShouldRemove() const + bool Window::olc_OnWindowClose() { - return bShouldRemove; + bRequestToClose = true; + return true; } - bool Window::olc_WindowUpdate(const float fElapsedTime, olc::gpu::Renderer* const gpu) + bool Window::olc_ShouldRemove() const { - // Environmental changes - - // Input Changes - mouse.UpdateState(); - - - draw.SetGPU(gpu); - draw.SetTarget(imgPrimary); - - - gpu->DisplayPrepare(); - - - gpu->AssignTextureTarget(0, imgPrimary.GetGPUID()); - gpu->SetViewport({ 0,0 }, imgPrimary.Size()); - //gpu->ClearViewport(olc::Colour::BLACK, true, true); - - gpu->ApplyDefaultShader(); - - // User Update - if (!OnUserUpdate(fElapsedTime)) - { - // User has requested termination of window by returning false - if (OnUserDestroy()) - { - // User has confirmed window destruction by returning true - bShouldRemove = true; - } - } - - - // Finialise any outstanding tasks - draw.ProcessGPUTasks(); - - // Take the window's completed "screen" and draw it as a textured quad to the backbuffer - gpu->AssignTextureTarget(0, 0); - gpu->SetViewport({ 0,0 }, GetSize()); - gpu->ClearViewport(olc::Colour::MAGENTA, true, true); - draw.ClearTransform(); - //draw.ImageRect(imgPrimary, { -1.0,1.0 }, { 2.0f,-2.0f }); - draw.ImageRect(imgPrimary, { 0.0,0.0 }, GetSize()); - draw.ProcessGPUTasks(); - - // Update Window's primary surface - gpu->DisplayDraw(); - - return true; + return bShouldRemove; } + size_t Window::GetUID() const { return nUniqueID; } - const olc::vi2d& Window::GetSize() const + const olc::vi2d& Window::GetWindowSize() const { return vWindowSize; } - const olc::vi2d& Window::GetPosition() const + const olc::vi2d& Window::GetWindowPosition() const { return vWindowPos; } - bool Window::SetSize(const olc::vi2d& vSize) + bool Window::SetWindowSize(const olc::vi2d& vSize) { // Request host to change geometry of window object @@ -147,7 +92,7 @@ namespace olc return true; } - bool Window::SetPosition(const olc::vi2d& vPosition) + bool Window::SetWindowPosition(const olc::vi2d& vPosition) { // Request host to change geometry of window object @@ -155,21 +100,17 @@ namespace olc return true; } - const std::string& Window::GetTitle() const + const std::string& Window::GetWindowTitle() const { return sFrameTitle; } - bool Window::SetTitle(const std::string& sTitle) + bool Window::SetWindowTitle(const std::string& sTitle) { sFrameTitle = sTitle; pHost->UpdateWindowFrameTitle(this); return false; } - bool Window::olc_PrimaryWindowInit() - { - return false; - } }; //! END IMPLEMENTATION \ No newline at end of file diff --git a/dev/src/window.h b/dev/src/window.h index d8c88600..11498286 100644 --- a/dev/src/window.h +++ b/dev/src/window.h @@ -14,7 +14,6 @@ #include "config.h" #include "pixel.h" #include "vector2d.h" -#include "gpu_iface.h" #include "draw2d.h" #include "hw_mouse.h" //! END CUSTOMHEADER @@ -39,6 +38,11 @@ namespace olc class Renderer; } + namespace imload + { + class ImageLoader; + } + namespace hw { class Mouse; @@ -52,27 +56,22 @@ namespace olc public: Window(); virtual ~Window(); + + void LinkToHost(olc::host::Host* host); - void ConnecToHost(olc::host::Host* host); + - public: - // Return true if window is to continue - virtual bool OnUserCreate(); - // Return true if window is to continue - virtual bool OnUserUpdate(float fElapsedTime); - // Return true if window is to close - virtual bool OnUserDestroy(); - - private: + private: // These are called externally from the host // Set Mouse Device State - bool olc_OnMouseButton(const uint8_t nButton, const bool bPressed); - bool olc_OnMouseMove(const olc::vi2d& vMousePos); - bool olc_OnMouseWheel(const int32_t nScroll); - bool olc_OnMouseFocus(const bool bHasFocus); + virtual bool olc_OnMouseButton(const uint8_t nButton, const bool bPressed); + virtual bool olc_OnMouseMove(const olc::vi2d& vMousePos); + virtual bool olc_OnMouseWheel(const int32_t nScroll); + virtual bool olc_OnMouseFocus(const bool bHasFocus); // Set Window State - bool olc_OnWindowPosition(const olc::vi2d& vWindowPos); - bool olc_OnWindowSize(const olc::vi2d& vWindowSize); + virtual bool olc_OnWindowPosition(const olc::vi2d& vWindowPos); + virtual bool olc_OnWindowSize(const olc::vi2d& vWindowSize); + virtual bool olc_OnWindowClose(); // Set Keyboard State @@ -80,25 +79,22 @@ namespace olc public: bool olc_ShouldRemove() const; - bool olc_WindowUpdate(const float fElapsedTime, olc::gpu::Renderer* const gpu); + public: size_t GetUID() const; - const olc::vi2d& GetSize() const; - bool SetSize(const olc::vi2d& vSize); - - const olc::vi2d& GetPosition() const; - bool SetPosition(const olc::vi2d& vPosition); + const olc::vi2d& GetWindowSize() const; + bool SetWindowSize(const olc::vi2d& vSize); - const std::string& GetTitle() const; - bool SetTitle(const std::string& sTitle); + const olc::vi2d& GetWindowPosition() const; + bool SetWindowPosition(const olc::vi2d& vPosition); + const std::string& GetWindowTitle() const; + bool SetWindowTitle(const std::string& sTitle); protected: - virtual bool olc_PrimaryWindowInit(); - - protected: + bool bRequestToClose = false; bool bShouldRemove = false; protected: @@ -107,17 +103,12 @@ namespace olc olc::vi2d vWindowSize; std::string sFrameTitle; - private: - olc::vi2d volatile_vMousePos; - + protected: olc::host::Host* pHost = nullptr; - //olc::gpu::Renderer* pRenderer = nullptr; protected: - olc::Draw2D draw; - olc::Image imgPrimary; - olc::hw::Mouse mouse; + }; } #define PGE_WINDOW_DECLARED 1 diff --git a/dev/tests/blend.png b/dev/tests/blend.png new file mode 100644 index 00000000..912ff431 Binary files /dev/null and b/dev/tests/blend.png differ diff --git a/dev/tests/test_mh.cpp b/dev/tests/test_mh.cpp index eaaccc16..25ed9dee 100644 --- a/dev/tests/test_mh.cpp +++ b/dev/tests/test_mh.cpp @@ -1,7 +1,33 @@ -#define OLC_PGE_APPLICATION +#define OLC_PGE3_APPLICATION #include "olcpge3.h" +class SecondWindow : public olc::PGEWindow +{ +public: + + // Return true if window is to continue + bool OnUserCreate() override + { + + return false; + } + + // Return true if window is to continue + bool OnUserUpdate(float fElapsedTime) override + { + fTotalTime += fElapsedTime; + + draw.Clear(olc::Colour::RED); + draw.WorldRotate(fTotalTime, GetDefaultImage().Size() / 2); + draw.FilledRect({ 10,10 }, { 20,20 }, olc::Colour::BLUE); + return true; + } + + + float fTotalTime = 0; +}; + class Example : public olc::PixelGameEngine { @@ -18,6 +44,9 @@ class Example : public olc::PixelGameEngine olc::Image imSampleTest; + olc::Image imLowRes; + olc::Image imHighResSprite; + void CreateSampleTestImage(olc::Image& image, const olc::vi2d& s) { CreateImage(image, s); @@ -50,23 +79,37 @@ class Example : public olc::PixelGameEngine { olc::vf2d pos; olc::vf2d vel; - float ang; - float angvel; + float ang = 0; + float angvel = 0; }; std::vector vecLogos; std::vector vecVerts; + float fScale = 1.0f; + int nSelectedVert = -1; + std::shared_ptr win2; + + olc::Image imBlend; + olc::Image imTempBuffer; + public: bool OnUserCreate() override { + + CreateImage(imLowRes, { 256, 240 }); + CreateImageFromFile(imHighResSprite, "e:/voxel.png"); + //CreateImage(imTest, { 64,64 }); CreateImageFromFile(imLogo, "../tests/olc.png"); + CreateImageFromFile(imBlend, "../tests/blend.png"); + + CreateImage(imTempBuffer, { 128, 128 }); - CreateSampleTestImage(imSampleTest, { 33, 33 }); + CreateSampleTestImage(imSampleTest, { 32, 32 }); CreateImage(imTemp, { 64, 64 }); @@ -74,18 +117,21 @@ class Example : public olc::PixelGameEngine vecLogos.resize(x); for (auto& a : vecLogos) { - a.pos = olc::vf2d(rand() % imgPrimary.Size().x, rand() % imgPrimary.Size().y); + a.pos = olc::vf2d(rand() % GetDefaultImage().Size().x, rand() % GetDefaultImage().Size().y); //a.vel = olc::vf2d(rand() % 100 - 50, rand() % 100 - 50); a.angvel = 1.1f; } vecVerts = { - { 200.0f, 200.0f}, - { 400.0f, 200.0f}, - { 400.0f, 400.0f}, - { 200.0f, 400.0f} + { 100.0f, 100.0f}, + { 200.0f, 100.0f}, + { 200.0f, 200.0f}, + { 100.0f, 200.0f} }; + + + return true; } @@ -105,13 +151,44 @@ class Example : public olc::PixelGameEngine //draw.FilledRect({ 0,0 }, imgPrimary.Size() * olc::vf2d(0.5f, 1.0f), olc::Pixel(0, 0, 0, 1)); - draw.Clear(olc::Colour::VERY_DARK_BLUE); + draw.SetTarget(imTempBuffer); + draw.Clear(olc::Colour::WHITE); + draw.Image(imBlend, { 0,0 }); + + + draw.SetTarget(GetDefaultImage()); + draw.WorldReset(); + draw.Clear(olc::Colour::TANGERINE); + //draw.Image(imHighResSprite, mouse.GetPosition()); + + + //draw.SetTarget(imLowRes); + + //draw.Clear(olc::Colour::BLANK); if (mouse.GetButton(1).bHeld) { fAngle += 0.2f * fElapsedTime; - draw.WorldRotate(fAngle, imgPrimary.Size() / 2.0f); + draw.WorldRotate(fAngle, imLowRes.Size() / 2.0f); } + + if (mouse.GetButton(2).bPressed) + { + auto w = std::make_shared(); + //AddChildWindow(w, { 100,100 }, { 2, 2 }); + } + + if (mouse.GetWheel() > 0) + { + fScale *= 1.1f; + } + + if (mouse.GetWheel() < 0) + { + fScale *= 0.9f; + } + + draw.WorldScale({ fScale, fScale }); //for (int x = 0; x < imgPrimary.Size().x; x++) // for (int y = 0; y < imgPrimary.Size().y; y++) // draw.Pixel(olc::vf2d( x, y ), olc::Pixel(rand() % 255, rand() % 255, rand() % 255)); @@ -120,8 +197,10 @@ class Example : public olc::PixelGameEngine //return true; + + //std::cout << mouse.GetPosition() << "\n"; - auto vMouse = draw.ScreenToWorld(mouse.GetPosition()); + auto vMouse = draw.ScreenToWorld(mouse.GetPosition() ); if (mouse.GetButton(0).bPressed) { @@ -156,17 +235,54 @@ class Example : public olc::PixelGameEngine //draw.Image(imTemp, vMouse, { 4,4 }); - draw.Image(imSampleTest, vMouse); + + + //draw.Image(olc::fontClassicPGE.imgFont, vMouse); + + + std::string sTest = "Hello World!\nThe quick brown fox JUMPS over the LaZy dog..."; + olc::vf2d vSizeMono = draw.GetTextSize(sTest, false, { 1, 2 }); + olc::vf2d vSizeProp = draw.GetTextSize(sTest, true, { 1, 2 }); + + + draw.String({ 10.0f, 10.0f }, "Hello World!\nThe quick brown fox JUMPS over the LaZy dog...", olc::Colour::WHITE, { 1, 2 }); + draw.Rect({ 10.0f, 10.0f }, vSizeMono, olc::Colour::GREEN); + + draw.StringProp({ 10.0f, 100.0f }, "Hello World!\nThe quick brown fox JUMPS over the LaZy dog...", olc::Colour::BLUE, { 1, 2 }); + draw.Rect({ 10.0f, 100.0f }, vSizeProp, olc::Colour::GREEN); + + draw.StringProp({ 10.0f, 200.0f }, "Hello World!\nThe quick brown fox JUMPS over the LaZy dog...", olc::PixelF(0.5f, 0.0f, 0.0f, 0.25f), {1, 2}); + draw.Rect({ 10.0f, 200.0f }, vSizeProp, olc::Colour::GREEN); //draw.Pixel(vMouse, olc::Colour::GREEN); //draw.Line(vMouse, vMouse + 1, olc::Colour::GREEN); + + draw.FilledRect({ 5, 100 }, { 100,100 }, olc::Colour::TANGERINE, olc::Colour::DARK_CYAN, olc::Colour::RED, olc::Colour::GREEN); + draw.FilledRect({ 5,5 }, { 10,10 }, olc::Colour::YELLOW); - draw.Rect({ 8,8 }, { 20,20 }, olc::Colour::RED); + + + + // draw.Rect({ 8,8 }, { 20,20 }, olc::Colour::RED); if(mouse.GetButton(0).bHeld) draw.Line({ 1.0f, 1.0f }, { 25.5f, 25.5f }); + + + //draw.Image(imTempBuffer, vMouse); + draw.Image(imSampleTest, vMouse); + //draw.SetTarget(imgPrimary); + //draw.WorldReset(); + //draw.Image(imLowRes, { 0,0 }, { 4, 4 }); + + + draw.swLine({ 100,100 }, vMouse, olc::Colour::BLACK, olc::Colour::YELLOW); + + draw.Rect({ 8,8, }, { 20,20 }, olc::Colour::RED, olc::Colour::GREEN, olc::Colour::BLUE, olc::Colour::WHITE); + draw.swRect({ 7,7, }, { 22,22 }, olc::Colour::RED, olc::Colour::GREEN, olc::Colour::BLUE, olc::Colour::WHITE); + //draw.Rect(vMouse, { 100,100 }); /*for (auto& a : vecLogos) @@ -296,8 +412,8 @@ int main() //if (demo.Construct({ 256, 240 }, { 4, 4 })) olc::PGEConfig cfg; - cfg.vPixelSize = { 4,4 }; - cfg.vScreenSize = { 256, 240 }; + cfg.vPixelSize = { 1,1 }; + cfg.vScreenSize = { 1024, 960 }; //cfg.bVSync = true; //if (demo.Construct({ 1280, 960 }, { 1, 1 }, cfg)) diff --git a/utilities/olcUTIL3_GameMode.h b/utilities/olcUTIL3_GameMode.h new file mode 100644 index 00000000..ac024266 --- /dev/null +++ b/utilities/olcUTIL3_GameMode.h @@ -0,0 +1,49 @@ +#pragma once + +#if defined(OLC_PGE3_MULTIFILE) + +#else + #include +#endif + +namespace olc::util3::gsm +{ + template + class Mode + { + public: + + Mode() = default; + + + Mode(std::shared_ptr pGlobalData) : m_pGlobalData(pGlobalData) + { + + } + + public: + virtual bool OnCreate(olc::PixelGameEngine* pge) + { + return false; + } + + virtual GState OnUpdate(olc::PixelGameEngine* pge, float fElapsedTime) + { + return GState{}; + } + + virtual bool OnEnterMode(olc::PixelGameEngine* pge) + { + return false; + } + + virtual bool OnExitMode(olc::PixelGameEngine* pge) + { + return false; + } + + protected: + std::shared_ptr m_pGlobalData; + }; + +} \ No newline at end of file