diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 0000000..4e45e63 --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,44 @@ +name: CD + +on: + push: + tags: + - 'v*' + workflow_dispatch: + +defaults: + run: + shell: bash + +# required for upload +permissions: + contents: write + +jobs: + + Linux: + runs-on: ${{ matrix.runner }} + strategy: + matrix: + runner: [ ubuntu-24.04, ubuntu-24.04-arm ] + steps: + - uses: actions/checkout@v4 + + - name: Install dependencies + run: sudo apt update -y && sudo apt install -y libsdl2-dev libglew-dev libpng-dev zip + + - name: Build + run: | + make -f Makefile.linux libprism.a + ARCH=`uname -m` + cd bin && zip -9 -r ../libprism-linux-${ARCH} libprism.a + + + - name: Create GitHub Release + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: | + *.zip \ No newline at end of file diff --git a/.gitignore b/.gitignore index 915668c..a0191f4 100644 --- a/.gitignore +++ b/.gitignore @@ -34,5 +34,6 @@ build/* *.iobj *.cache *.psd +*.a -bin/* \ No newline at end of file +bin/* diff --git a/Makefile.linux b/Makefile.linux new file mode 100644 index 0000000..448f5c2 --- /dev/null +++ b/Makefile.linux @@ -0,0 +1,24 @@ +include Makefile.common + +OBJS += linux/drawing_linux.o linux/texture_linux.o linux/input_linux.o \ +linux/framerateselect_linux.o linux/file_linux.o linux/math_linux.o linux/system_linux.o linux/log_linux.o \ +linux/soundeffect_linux.o linux/sound_linux.o linux/memoryhandler_linux.o web/netplay_web.o linux/romdisk_linux.o \ +linux/saveload_linux.o linux/screeneffect_linux.o linux/logoscreen_linux.o \ +web/thread_web.o + +CXXFLAGS += -DNDEBUG -I ./include -I ./external/imgui/inc `sdl2-config --cflags` -std=c++17 -O2 + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(AR) -rc $@ $^ + +%.o : %.cpp + $(CC) -c $(CXXFLAGS) -o $@ $< + +%.o : %.c + $(CC) -c -o $@ $< + +clean: + -rm -f *.o + -rm -rf linux/*.o \ No newline at end of file diff --git a/README.md b/README.md index a97b6f6..5debd23 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ -# Prism - Library for Dreamcast / Windows game development +[![CD](https://github.com/humbertodias/prism/actions/workflows/cd.yml/badge.svg)](https://github.com/humbertodias/prism/actions/workflows/cd.yml) -A loose collection of components that are often used in games (e.g.: physics, collisions, animations, etc.). The Dreamcast part requires KallistiOS, the Windows part requires SDL. +# Prism - Library for Dreamcast / Windows / Linux game development + +A loose collection of components that are often used in games (e.g.: physics, collisions, animations, etc.). The Dreamcast part requires KallistiOS, the Windows/Linux part requires SDL2. ## Dreamcast Usage Clone in your `$(KOS_ROOT)/addons` folder and compile this library with `make`. diff --git a/include/prism/datastructures.h b/include/prism/datastructures.h index ff9bd2d..86e190f 100644 --- a/include/prism/datastructures.h +++ b/include/prism/datastructures.h @@ -2,6 +2,7 @@ #include #include +#include namespace prism { diff --git a/include/prism/file.h b/include/prism/file.h index 40725ff..6ddf355 100644 --- a/include/prism/file.h +++ b/include/prism/file.h @@ -18,7 +18,7 @@ namespace prism { typedef int FileHandler; -#elif defined _WIN32 || defined __EMSCRIPTEN__ || defined VITA +#elif defined _WIN32 || defined __EMSCRIPTEN__ || defined VITA || defined __linux__ #define O_RDONLY 0x1 #define O_WRONLY 0x2 diff --git a/include/prism/memoryhandler.h b/include/prism/memoryhandler.h index 244514e..c090cc4 100644 --- a/include/prism/memoryhandler.h +++ b/include/prism/memoryhandler.h @@ -9,7 +9,7 @@ namespace prism { typedef pvr_ptr_t Texture; -#elif defined _WIN32 || defined __EMSCRIPTEN__ +#elif defined _WIN32 || defined __EMSCRIPTEN__ || defined __linux__ #include #include @@ -81,7 +81,7 @@ int getAvailableSoundMemory(); int getAllocatedMemoryBlockAmount(); -#ifdef _WIN32 +#if defined(_WIN32) || defined(__linux__) void imguiMemoryHandler(); void imguiTextureMemory(const std::string_view& tName, const TextureMemory& tTextureMemory); #endif diff --git a/include/prism/memorypool.h b/include/prism/memorypool.h index e03eb00..b12c795 100644 --- a/include/prism/memorypool.h +++ b/include/prism/memorypool.h @@ -3,6 +3,7 @@ #include #include #include +#include namespace prism { diff --git a/include/prism/system.h b/include/prism/system.h index df196a5..d700a35 100644 --- a/include/prism/system.h +++ b/include/prism/system.h @@ -60,6 +60,7 @@ double getInverseFramerateFactor(); void setVGA(); int isOnDreamcast(); int isOnWindows(); +int isOnLinux(); int isOnWeb(); int isOnVita(); diff --git a/linux/debugimgui_linux.cpp b/linux/debugimgui_linux.cpp new file mode 100644 index 0000000..39051cc --- /dev/null +++ b/linux/debugimgui_linux.cpp @@ -0,0 +1,112 @@ +#include "prism/windows/debugimgui_win.h" + +#include +#include +#include +#include + +#include + +namespace prism { + + extern SDL_Window* gSDLWindow; + extern SDL_GLContext gGLContext; + + static struct { + bool mIsActive; + + } gImguiPrismData; + + bool isImguiPrismActive() + { + return gImguiPrismData.mIsActive; + } + + void imguiPrismInitAfterDrawingSetup() + { + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + + // Setup Platform/Renderer backends + ImGui_ImplSDL2_InitForOpenGL(gSDLWindow, gGLContext); + ImGui_ImplOpenGL3_Init(); + + gImguiPrismData.mIsActive = isInDevelopMode(); + gImguiPrismData.mIsActive = false; + } + + void imguiPrismProcessEvent(SDL_Event* tEvent) + { + ImGui_ImplSDL2_ProcessEvent(tEvent); + } + + static bool testWindow = false; + + void imguiPrismStartFrame() + { + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + } + + + void imguiPrismRenderStart() + { + ImGuiIO& io = ImGui::GetIO(); (void)io; + + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu("Prism")) + { + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Screen")) + { + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("Blitz")) + { + ImGui::EndMenu(); + } + + ImGui::SameLine(ImGui::GetWindowWidth() - 140); + ImGui::Text("%.1f ms (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + + ImGui::EndMainMenuBar(); + } + } + + void imguiPrismRenderEnd() + { + + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + } + + void imguiPrismShutdown() + { + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + } + + void imguiPrismAddTab(const std::string_view& tTabName, const std::string_view& tEntryName, bool* tBool) + { + if (ImGui::BeginMainMenuBar()) + { + if (ImGui::BeginMenu(tTabName.data())) + { + ImGui::MenuItem(tEntryName.data(), NULL, tBool); + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } + } +} \ No newline at end of file diff --git a/linux/drawing_linux.cpp b/linux/drawing_linux.cpp new file mode 100644 index 0000000..669561c --- /dev/null +++ b/linux/drawing_linux.cpp @@ -0,0 +1,1226 @@ +#include +#include +#include +#include +#include +#include "prism/windows/debugimgui_win.h" + + +#include "prism/log.h" +#include "prism/system.h" +#include "prism/datastructures.h" +#include "prism/memoryhandler.h" +#include "prism/math.h" +#include "prism/stlutil.h" +#include "prism/debug.h" +#include "prism/geometry.h" +#include "prism/drawing.h" + +namespace prism { + +static const GLchar *gVertexShader = +"uniform mat4 ProjMtx;\n" +"attribute vec2 Position;\n" +"attribute vec2 UV;\n" +"attribute vec4 Color;\n" +"attribute vec3 ColorOffset;\n" +"varying vec2 Frag_UV;\n" +"varying vec4 Frag_Color;\n" +"varying vec3 Frag_ColorOffset;\n" +"void main()\n" +"{\n" +" Frag_UV = UV;\n" +" Frag_Color = Color;\n" +" Frag_ColorOffset = ColorOffset;\n" +" gl_Position = ProjMtx * vec4(Position.xy, 0, 1);\n" +"}\n"; + +static const GLchar* gFragmentShader = +#ifdef __EMSCRIPTEN__ +// WebGL requires precision specifiers but OpenGL 2.1 disallows +// them, so I define the shader without it and then add it here. +"precision mediump float;\n" +#endif +"uniform sampler2D Texture;\n" +"uniform sampler2D Palette;\n" +"uniform sampler2D BG;\n" +"uniform ivec3 ScreenSizeBlendStyle;\n" +"uniform vec2 DestinationAlphaColorFactor;\n" +"uniform ivec3 PaletteSolidityInversionUsed;\n" +"varying vec2 Frag_UV;\n" +"varying vec4 Frag_Color;\n" +"varying vec3 Frag_ColorOffset;\n" +"void main()\n" +"{\n" +" vec4 textureColor;\n" +" if(PaletteSolidityInversionUsed.x == 1) {\n" +" vec4 index = texture2D(Texture, Frag_UV);\n" +" textureColor = texture2D(Palette, vec2(index.w, 0.0));\n" +" } else {\n" +" textureColor = texture2D(Texture, Frag_UV);\n" +" }\n" +" if(DestinationAlphaColorFactor.y != 1.0) {\n" +" vec3 grayscaleColor = vec3((textureColor.x + textureColor.y + textureColor.z) / 3.0);\n" +" textureColor.xyz = DestinationAlphaColorFactor.y * textureColor.xyz + (1.0 - DestinationAlphaColorFactor.y) * grayscaleColor;\n" +" }\n" +" if(PaletteSolidityInversionUsed.z == 1) {\n" +" textureColor.xyz = vec3(1.0) - textureColor.xyz;\n" +" }\n" +" vec4 srcColor;\n" +" if(PaletteSolidityInversionUsed.y == 0) {\n" +" srcColor = Frag_Color * (vec4(Frag_ColorOffset, 0) + textureColor);\n" +" } else {\n" +" srcColor = vec4(Frag_Color.xyz, Frag_Color.w * textureColor.w);\n" +" }\n" +" if(ScreenSizeBlendStyle.z == 0) {\n" +" gl_FragColor = srcColor;\n" +" } else {\n" +" vec4 dstColor = texture2D(BG, gl_FragCoord.xy / vec2(ScreenSizeBlendStyle.xy));\n" +" float blendFactor = (ScreenSizeBlendStyle.z == 1) ? 1.0 : -1.0;\n" +" float destinationBlendFactor = (srcColor.w == 0.0) ? 1.0 : DestinationAlphaColorFactor.x;\n" +" gl_FragColor = vec4(dstColor.xyz * destinationBlendFactor + blendFactor * srcColor.xyz * srcColor.w, dstColor.w);\n" +" }\n" +"}\n"; + +using namespace std; + +typedef struct { + + double a; + double r; + double g; + double b; + double rOffset; + double gOffset; + double bOffset; + + Matrix4D mTransformationMatrix; + + Vector mEffectStack; + + int mIsDisabled; + + BlendType mBlendType; + int mIsColorSolid; + int mIsColorInversed; + double mDestAlpha; + double mColorFactor; + + GLuint mPalettes[4]; +} DrawingData; + +struct DrawListSpriteElement{ + TextureData mTexture; + Position2D mTopLeft; + Position2D mTopRight; + Position2D mBottomLeft; + Position2D mBottomRight; + PrismRectangle mTexturePosition; + + DrawingData mData; + double mZ; +} ; + +struct DrawListTruetypeElement{ + char mText[1024]; + TTF_Font* mFont; + Position2D mPos; + Vector3DI mTextSize; + Vector3D mColor; + double mTextBoxWidth; + GeoRectangle2D mDrawRectangle; + + DrawingData mData; + double mZ; +}; + +class DrawListElement { +public: + enum Type { + DRAW_LIST_ELEMENT_TYPE_SPRITE, + DRAW_LIST_ELEMENT_TYPE_TRUETYPE, + }; + + DrawListElement(DrawListSpriteElement e) { + impl_.mSprite = e; + mType = Type::DRAW_LIST_ELEMENT_TYPE_SPRITE; + } + + DrawListElement(DrawListTruetypeElement e) { + impl_.mTrueType = e; + mType = Type::DRAW_LIST_ELEMENT_TYPE_TRUETYPE; + } + + ~DrawListElement() { + if (mType == DRAW_LIST_ELEMENT_TYPE_SPRITE) { + impl_.mSprite.~DrawListSpriteElement(); + } + else { + impl_.mTrueType.~DrawListTruetypeElement(); + } + } + + double getZ() const { + if (mType == DRAW_LIST_ELEMENT_TYPE_SPRITE) { + return impl_.mSprite.mZ; + } + else { + return impl_.mTrueType.mZ; + } + } + + operator DrawListSpriteElement() const { + return impl_.mSprite; + } + + const DrawListSpriteElement* asSpriteElement() const { + return &impl_.mSprite; + } + + operator DrawListTruetypeElement() const { + return impl_.mTrueType; + } + + const DrawListTruetypeElement* asTruetypeElement() const { + return &impl_.mTrueType; + } + + bool operator<(const DrawListElement& other) const { + return getZ() < other.getZ(); + } + + Type mType; + +private: + union Impl { + DrawListSpriteElement mSprite; + DrawListTruetypeElement mTrueType; + Impl() {} + } impl_; +}; + +enum GraphicsCardType { + INTEL, + NVIDIA, + AMD, + WEBGL, + UNKNOWN +}; + +enum ShaderBlendType { + SHADER_BLEND_TYPE_NORMAL = 0, + SHADER_BLEND_TYPE_ADDITION = 1, + SHADER_BLEND_TYPE_SUBTRACTION = 2, +}; + +typedef struct { + int mShaderHandle, mVertHandle, mFragHandle; + int mAttribLocationTex, mAttribLocationPal, mAttribLocationBG, mAttribLocationProjMtx, mAttribLocationScreenSizeBlendStyle, mAttribLocationDestinationAlphaColorFactor, mAttribLocationPaletteSolidityInversionUsed; + int mAttribLocationPosition, mAttribLocationUV, mAttribLocationColor, mAttribLocationColorOffset; +} PrismShader; + +static struct { + unsigned int mVboHandle, mElementsHandle, mFBO, mFBOColorAttachment; + PrismShader mPrismShader; + + GraphicsCardType mCardType; + Vector3D mScreenScale; + Vector3D mRealScreenSize; + + uint32_t mSubtractionEquation; + uint32_t mActiveShaderFlags; +} gOpenGLData; + +static struct { + double mFrequency; + double mFrameStartTime; + double mRealFramerate = 60; + + bool mIsFrameSkippingEnabled; + double mDrawingStartTime; + double mDrawingEndTime; + int mIsSkippingNextFrameDrawCounter; + double mRealFrameStartTime; +} gBookkeepingData; + +static multiset gDrawVector; +static DrawingData gPrismWindowsDrawingData; + +extern SDL_Window* gSDLWindow; + +static void imguiOpenGLData() +{ + if (ImGui::TreeNode("OpenGL Data")) + { + ImGui::Text("Vendor: %s", (char*)glGetString(GL_VENDOR)); + ImGui::Text("Renderer: %s", (char*)glGetString(GL_RENDERER)); + ImGui::Text("Version: %s", (char*)glGetString(GL_VERSION)); + ImGui::Text("GLSL: %s", (char*)glGetString(GL_SHADING_LANGUAGE_VERSION)); + ImGui::Text("Card Type: %d", gOpenGLData.mCardType); + ImGui::Text("Screen Scale: %f %f %f", gOpenGLData.mScreenScale.x, gOpenGLData.mScreenScale.y, gOpenGLData.mScreenScale.z); + ImGui::Text("Real Screen Size: %f %f %f", gOpenGLData.mRealScreenSize.x, gOpenGLData.mRealScreenSize.y, gOpenGLData.mRealScreenSize.z); + ImGui::Text("Subtraction Equation: %d", gOpenGLData.mSubtractionEquation); + ImGui::Text("Active Shader Flags: %d", gOpenGLData.mActiveShaderFlags); + ImGui::TreePop(); + } +} + +static void imguiBookkeepingData() +{ + if (ImGui::TreeNode("Bookkeeping Data")) + { + ImGui::Text("Frequency: %f", gBookkeepingData.mFrequency); + ImGui::Text("Frame Start Time: %f", gBookkeepingData.mFrameStartTime); + ImGui::Text("Real Framerate: %f", gBookkeepingData.mRealFramerate); + ImGui::Text("Is Frame Skipping Enabled: %d", gBookkeepingData.mIsFrameSkippingEnabled); + ImGui::Text("Drawing Start Time: %f", gBookkeepingData.mDrawingStartTime); + ImGui::Text("Drawing End Time: %f", gBookkeepingData.mDrawingEndTime); + ImGui::Text("Is Skipping Next Frame Draw Counter: %d", gBookkeepingData.mIsSkippingNextFrameDrawCounter); + ImGui::Text("Real Frame Start Time: %f", gBookkeepingData.mRealFrameStartTime); + ImGui::TreePop(); + } +} + +static void imguiDrawVector() +{ + if (ImGui::TreeNode("Draw Vector")) + { + auto drawVectorElement = gDrawVector.begin(); + for (size_t i = 0; i < gDrawVector.size(); i++) + { + if (ImGui::TreeNode(std::to_string(i).c_str())) + { + const auto& element = *drawVectorElement; + if (element.mType == DrawListElement::Type::DRAW_LIST_ELEMENT_TYPE_SPRITE) + { + const auto spriteElement = element.asSpriteElement(); + ImGui::Text("Type: Sprite"); + ImGui::Text("Texture size: %d %d", spriteElement->mTexture.mTextureSize.x, spriteElement->mTexture.mTextureSize.y); + ImGui::Text("Top Left: %f %f", spriteElement->mTopLeft.x, spriteElement->mTopLeft.y); + ImGui::Text("Top Right: %f %f", spriteElement->mTopRight.x, spriteElement->mTopRight.y); + ImGui::Text("Bottom Left: %f %f", spriteElement->mBottomLeft.x, spriteElement->mBottomLeft.y); + ImGui::Text("Bottom Right: %f %f", spriteElement->mBottomRight.x, spriteElement->mBottomRight.y); + ImGui::Text("Texture Position: %d %d %d %d", spriteElement->mTexturePosition.topLeft.x, spriteElement->mTexturePosition.topLeft.y, spriteElement->mTexturePosition.bottomRight.x, spriteElement->mTexturePosition.bottomRight.y); + ImGui::Text("Z: %f", spriteElement->mZ); + } + else + { + const auto truetypeElement = element.asTruetypeElement(); + ImGui::Text("Type: Truetype"); + ImGui::Text("Text: %s", truetypeElement->mText); + ImGui::Text("Font: %p", truetypeElement->mFont); + ImGui::Text("Pos: %f %f", truetypeElement->mPos.x, truetypeElement->mPos.y); + ImGui::Text("Text Size: %d %d %d", truetypeElement->mTextSize.x, truetypeElement->mTextSize.y, truetypeElement->mTextSize.z); + ImGui::Text("Color: %f %f %f", truetypeElement->mColor.x, truetypeElement->mColor.y, truetypeElement->mColor.z); + ImGui::Text("TextBoxWidth: %f", truetypeElement->mTextBoxWidth); + ImGui::Text("DrawRectangle: %f %f %f %f", truetypeElement->mDrawRectangle.mTopLeft.x, truetypeElement->mDrawRectangle.mTopLeft.y, truetypeElement->mDrawRectangle.mBottomRight.x, truetypeElement->mDrawRectangle.mBottomRight.y); + ImGui::Text("Z: %f", truetypeElement->mZ); + } + ImGui::TreePop(); + } + drawVectorElement++; + } + ImGui::TreePop(); + } +} + +static void imguiPrimsWindowsDrawingData() +{ + if (ImGui::TreeNode("Prism Windows Drawing Data")) + { + ImGui::Text("a: %f", gPrismWindowsDrawingData.a); + ImGui::Text("r: %f", gPrismWindowsDrawingData.r); + ImGui::Text("g: %f", gPrismWindowsDrawingData.g); + ImGui::Text("b: %f", gPrismWindowsDrawingData.b); + ImGui::Text("rOffset: %f", gPrismWindowsDrawingData.rOffset); + ImGui::Text("gOffset: %f", gPrismWindowsDrawingData.gOffset); + ImGui::Text("bOffset: %f", gPrismWindowsDrawingData.bOffset); + ImGui::Text("Transformation Matrix: %p", (void*)&gPrismWindowsDrawingData.mTransformationMatrix); + ImGui::Text("Effect Stack: %p", (void*)&gPrismWindowsDrawingData.mEffectStack); + ImGui::Text("Is Disabled: %d", gPrismWindowsDrawingData.mIsDisabled); + ImGui::Text("Blend Type: %d", gPrismWindowsDrawingData.mBlendType); + ImGui::Text("Is Color Solid: %d", gPrismWindowsDrawingData.mIsColorSolid); + ImGui::Text("Is Color Inversed: %d", gPrismWindowsDrawingData.mIsColorInversed); + ImGui::Text("Dest Alpha: %f", gPrismWindowsDrawingData.mDestAlpha); + ImGui::Text("Color Factor: %f", gPrismWindowsDrawingData.mColorFactor); + ImGui::Text("Palettes: %u %u %u %u", gPrismWindowsDrawingData.mPalettes[0], gPrismWindowsDrawingData.mPalettes[1], gPrismWindowsDrawingData.mPalettes[2], gPrismWindowsDrawingData.mPalettes[3]); + ImGui::TreePop(); + } +} + +void imguiDrawingHardware() +{ + static bool isWindowShown = false; + imguiPrismAddTab("Prism", "Drawing HW", &isWindowShown); + if (isWindowShown) + { + ImGui::Begin("Drawing HW", &isWindowShown); + imguiOpenGLData(); + imguiBookkeepingData(); + imguiDrawVector(); + imguiPrimsWindowsDrawingData(); + ImGui::End(); + } + +} +static void detectOpenGLCardType() { + auto vendorString = std::string((char*)glGetString(GL_VENDOR)); + turnStringLowercase(vendorString); + const auto vendorStringsSplit = splitStringBySeparator(vendorString, ' '); + + if (std::find(vendorStringsSplit.begin(), vendorStringsSplit.end(), "intel") != vendorStringsSplit.end()) { + gOpenGLData.mCardType = GraphicsCardType::INTEL; + } + else if (std::find(vendorStringsSplit.begin(), vendorStringsSplit.end(), "nvidia") != vendorStringsSplit.end()) { + gOpenGLData.mCardType = GraphicsCardType::NVIDIA; + } + else if (std::find(vendorStringsSplit.begin(), vendorStringsSplit.end(), "ati") != vendorStringsSplit.end() || std::find(vendorStringsSplit.begin(), vendorStringsSplit.end(), "amd") != vendorStringsSplit.end()) { + gOpenGLData.mCardType = GraphicsCardType::AMD; + } + else if (isOnWeb()) { + gOpenGLData.mCardType = GraphicsCardType::WEBGL; + } + else { + logWarningFormat("Unable to detect GPU properly: %s %s", (char*)glGetString(GL_VENDOR), (char*)glGetString(GL_RENDERER)); + gOpenGLData.mCardType = GraphicsCardType::UNKNOWN; + } +} + +static void setupCardSpecificRendering() { + if (gOpenGLData.mCardType == GraphicsCardType::INTEL) { + gOpenGLData.mSubtractionEquation = GL_FUNC_ADD; + } + else { + gOpenGLData.mSubtractionEquation = GL_FUNC_REVERSE_SUBTRACT; + } +} + +static void initPrismShaderGeneral(PrismShader& tShader, const GLchar* tFragmentShader) { + tShader.mShaderHandle = glCreateProgram(); + tShader.mVertHandle = glCreateShader(GL_VERTEX_SHADER); + tShader.mFragHandle = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(tShader.mVertHandle, 1, &gVertexShader, NULL); + glShaderSource(tShader.mFragHandle, 1, &tFragmentShader, 0); + glCompileShader(tShader.mVertHandle); + glCompileShader(tShader.mFragHandle); + glAttachShader(tShader.mShaderHandle, tShader.mVertHandle); + glAttachShader(tShader.mShaderHandle, tShader.mFragHandle); + glLinkProgram(tShader.mShaderHandle); + + tShader.mAttribLocationTex = glGetUniformLocation(tShader.mShaderHandle, "Texture"); + tShader.mAttribLocationPal = glGetUniformLocation(tShader.mShaderHandle, "Palette"); + tShader.mAttribLocationBG = glGetUniformLocation(tShader.mShaderHandle, "BG"); + tShader.mAttribLocationProjMtx = glGetUniformLocation(tShader.mShaderHandle, "ProjMtx"); + tShader.mAttribLocationScreenSizeBlendStyle = glGetUniformLocation(tShader.mShaderHandle, "ScreenSizeBlendStyle"); + tShader.mAttribLocationDestinationAlphaColorFactor = glGetUniformLocation(tShader.mShaderHandle, "DestinationAlphaColorFactor"); + tShader.mAttribLocationPaletteSolidityInversionUsed = glGetUniformLocation(tShader.mShaderHandle, "PaletteSolidityInversionUsed"); + tShader.mAttribLocationPosition = glGetAttribLocation(tShader.mShaderHandle, "Position"); + tShader.mAttribLocationUV = glGetAttribLocation(tShader.mShaderHandle, "UV"); + tShader.mAttribLocationColor = glGetAttribLocation(tShader.mShaderHandle, "Color"); + tShader.mAttribLocationColorOffset = glGetAttribLocation(tShader.mShaderHandle, "ColorOffset"); +} + +static void initPrismShader() { + initPrismShaderGeneral(gOpenGLData.mPrismShader, gFragmentShader); +} + +static void initShaders() { + initPrismShader(); +} + +static void useShaderGeneral() { + glBindBuffer(GL_ARRAY_BUFFER, gOpenGLData.mVboHandle); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, gOpenGLData.mElementsHandle); + + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_SCISSOR_TEST); + glActiveTexture(GL_TEXTURE0); + + GLuint elements[] = { + 0, 1, 2, + 2, 3, 0 + }; + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STREAM_DRAW); + + glViewport(0, 0, (GLsizei)gOpenGLData.mRealScreenSize.x, (GLsizei)gOpenGLData.mRealScreenSize.y); + glClearColor(0, 0, 0, 1); +} + +static void usePrismShaderGeneral(const PrismShader& tShader) { + glUseProgram(tShader.mShaderHandle); + glUniform1i(tShader.mAttribLocationTex, 0); + glUniform1i(tShader.mAttribLocationPal, 1); + glUniform1i(tShader.mAttribLocationBG, 2); + + // Render command lists + glBindBuffer(GL_ARRAY_BUFFER, gOpenGLData.mVboHandle); + glEnableVertexAttribArray(tShader.mAttribLocationPosition); + glEnableVertexAttribArray(tShader.mAttribLocationUV); + glEnableVertexAttribArray(tShader.mAttribLocationColor); + glEnableVertexAttribArray(tShader.mAttribLocationColorOffset); + int stride = sizeof(GLfloat) * 11; + glVertexAttribPointer(tShader.mAttribLocationPosition, 2, GL_FLOAT, GL_FALSE, stride, 0); + glVertexAttribPointer(tShader.mAttribLocationUV, 2, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof(GLfloat) * 2)); + glVertexAttribPointer(tShader.mAttribLocationColor, 4, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof(GLfloat) * 4)); + glVertexAttribPointer(tShader.mAttribLocationColorOffset, 3, GL_FLOAT, GL_FALSE, stride, (void*)(sizeof(GLfloat) * 8)); + + useShaderGeneral(); +} + +static void usePrismShader() { + usePrismShaderGeneral(gOpenGLData.mPrismShader); +} + +#ifndef __EMSCRIPTEN__ +static void createFBO(GLuint* tTarget, GLuint* tColorBuffer) { + glGenFramebuffers(1, tTarget); + glBindFramebuffer(GL_FRAMEBUFFER, *tTarget); + + glGenTextures(1, tColorBuffer); + glBindTexture(GL_TEXTURE_2D, *tColorBuffer); + debugFormat("Creating fbo with size %d %d\n", (GLsizei)gOpenGLData.mRealScreenSize.x, (GLsizei)gOpenGLData.mRealScreenSize.y); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)gOpenGLData.mRealScreenSize.x, (GLsizei)gOpenGLData.mRealScreenSize.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *tColorBuffer, 0); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + logError("Framebuffer is not complete!"); + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +static void destroyFBO(GLuint* tTarget, GLuint* tColorBuffer) { + glDeleteTextures(1, tColorBuffer); + glDeleteFramebuffers(1, tTarget); +} + +static void initFBOs() { + createFBO(&gOpenGLData.mFBO, &gOpenGLData.mFBOColorAttachment); +} + +static void unloadFBOs() { + destroyFBO(&gOpenGLData.mFBO, &gOpenGLData.mFBOColorAttachment); +} + +static void recreateFBOs() { + unloadFBOs(); + initFBOs(); +} +#endif + +#ifndef __EMSCRIPTEN__ +static void GLAPIENTRY +MessageCallback(GLenum /*source*/, + GLenum type, + GLuint /*id*/, + GLenum severity, + GLsizei /*length*/, + const GLchar* message, + const void* /*userParam*/) +{ + if (severity == 0x826b) return; + logErrorFormat("GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n", + (type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : ""), + type, severity, message); +} +#endif + +static void initOpenGL() { +#ifndef __EMSCRIPTEN__ + if (isInDevelopMode()) { + glEnable(GL_DEBUG_OUTPUT); + glDebugMessageCallback(MessageCallback, 0); + } +#endif + initShaders(); + + glGenBuffers(1, &gOpenGLData.mVboHandle); + glGenBuffers(1, &gOpenGLData.mElementsHandle); +#ifndef __EMSCRIPTEN__ + initFBOs(); +#endif + + usePrismShader(); + + for (int i = 0; i < 4; i++) { + glGenTextures(1, &gPrismWindowsDrawingData.mPalettes[i]); + } + + detectOpenGLCardType(); + setupCardSpecificRendering(); +} + +void setDrawingScreenScale(double tScaleX, double tScaleY); + +void initDrawing() { + if (gSDLWindow == NULL) { + logError("Unable to init drawing without SDL window."); + recoverFromError(); + } + + ScreenSize sz = getScreenSize(); + setDrawingScreenScale((640.0 / sz.x), (480.0 / sz.y)); + setDrawingParametersToIdentity(); + + IMG_Init(IMG_INIT_PNG); + TTF_Init(); + + gDrawVector.clear(); + + struct timespec counter; + clock_gettime(CLOCK_MONOTONIC, &counter); + gBookkeepingData.mFrequency = counter.tv_sec + (counter.tv_nsec / 1e9); + gBookkeepingData.mFrameStartTime = 0; + gBookkeepingData.mIsFrameSkippingEnabled = false; + gBookkeepingData.mDrawingStartTime = 0; + gBookkeepingData.mDrawingEndTime = 0; + gBookkeepingData.mIsSkippingNextFrameDrawCounter = 0; + gBookkeepingData.mRealFrameStartTime = 0; + + gPrismWindowsDrawingData.mEffectStack = new_vector(); + + gPrismWindowsDrawingData.mIsDisabled = 0; + + initOpenGL(); +} + +static int isCulledOutsideScreen(const Position& tPos, const PrismRectangle& tTexturePosition) { + setProfilingSectionMarkerCurrentFunction(); + + const auto sizeX = abs(tTexturePosition.bottomRight.x - tTexturePosition.topLeft.x) + 1; + const auto sizeY = abs(tTexturePosition.bottomRight.y - tTexturePosition.topLeft.y) + 1; + std::vector corners; + corners.push_back(rotateScaleTranslatePositionByMatrix4D(gPrismWindowsDrawingData.mTransformationMatrix, tPos)); + corners.push_back(rotateScaleTranslatePositionByMatrix4D(gPrismWindowsDrawingData.mTransformationMatrix, tPos + Vector3D(sizeX, 0, 0))); + corners.push_back(rotateScaleTranslatePositionByMatrix4D(gPrismWindowsDrawingData.mTransformationMatrix, tPos + Vector3D(0, sizeY, 0))); + corners.push_back(rotateScaleTranslatePositionByMatrix4D(gPrismWindowsDrawingData.mTransformationMatrix, tPos + Vector3D(sizeX, sizeY, 0))); + const auto minX = min(corners[0].x, min(corners[1].x, min(corners[2].x, corners[3].x))); + const auto maxX = max(corners[0].x, max(corners[1].x, max(corners[2].x, corners[3].x))); + const auto minY = min(corners[0].y, min(corners[1].y, min(corners[2].y, corners[3].y))); + const auto maxY = max(corners[0].y, max(corners[1].y, max(corners[2].y, corners[3].y))); + static const auto CULL_EPSILON = 1e-5; + if (maxX < -1 - CULL_EPSILON) return 1; + if (minX > 1 + CULL_EPSILON) return 1; + if (maxY < -1 - CULL_EPSILON) return 1; + if (minY > 1 + CULL_EPSILON) return 1; + return 0; +} + +void drawSprite(const TextureData& tTexture, const Position& tPos, const PrismRectangle& tTexturePosition) { + setProfilingSectionMarkerCurrentFunction(); + if (gPrismWindowsDrawingData.mIsDisabled) return; + if (isCulledOutsideScreen(tPos, tTexturePosition)) return; + + assert(tPos.z >= 0); + const auto sizeX = abs(tTexturePosition.bottomRight.x - tTexturePosition.topLeft.x) + 1; + const auto sizeY = abs(tTexturePosition.bottomRight.y - tTexturePosition.topLeft.y) + 1; + drawSpriteNoRectangle(tTexture, tPos, Vector3D(tPos.x + sizeX, tPos.y, tPos.z), Vector3D(tPos.x, tPos.y + sizeY, tPos.z), Vector3D(tPos.x + sizeX, tPos.y + sizeY, tPos.z), tTexturePosition); +} + + +void drawSpriteNoRectangle(const TextureData& tTexture, const Position& tTopLeft, const Position& tTopRight, const Position& tBottomLeft, const Position& tBottomRight, const PrismRectangle& tTexturePosition) +{ + setProfilingSectionMarkerCurrentFunction(); + if (gPrismWindowsDrawingData.mIsDisabled) return; + verboseLog("Draw Sprite"); + verboseInteger(tTexture.mTextureSize.x); + verboseInteger(tTexture.mTextureSize.y); + + if (tTexture.mTextureSize.x < 0 || tTexture.mTextureSize.y < 0) { + logError("Called with invalid textureSize"); + logErrorInteger(tTexture.mTextureSize.x); + logErrorInteger(tTexture.mTextureSize.y); + + return; + } + + DrawListSpriteElement e; + e.mTexture = tTexture; + e.mTopLeft = tTopLeft.xy(); + e.mTopRight = tTopRight.xy(); + e.mBottomLeft = tBottomLeft.xy(); + e.mBottomRight = tBottomRight.xy(); + e.mTexturePosition = tTexturePosition; + e.mData = gPrismWindowsDrawingData; + e.mZ = tTopLeft.z; + gDrawVector.insert(DrawListElement(e)); +} + +static void clearDrawVector() { + gDrawVector.clear(); +} + +static void startDrawingBookkeeping() +{ + struct timespec counter; + clock_gettime(CLOCK_MONOTONIC, &counter); + gBookkeepingData.mDrawingStartTime = counter.tv_sec + (counter.tv_nsec / 1e9); +} + +void startDrawing() { + setProfilingSectionMarkerCurrentFunction(); + + startDrawingBookkeeping(); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glClear(GL_COLOR_BUFFER_BIT); + glBindFramebuffer(GL_FRAMEBUFFER, gOpenGLData.mFBO); + glClear(GL_COLOR_BUFFER_BIT); + clearDrawVector(); +} + +static const PrismShader& getActivePrismShaderReference() { + return gOpenGLData.mPrismShader; +} + +static void setSingleVertex(GLfloat* tDst, const Position2D& tPosition, double tU, double tV, const Position& tColor, double tAlpha, const Position& tColorOffset) { + tDst[0] = (GLfloat)tPosition.x; + tDst[1] = (GLfloat)tPosition.y; + tDst[2] = (GLfloat)tU; + tDst[3] = (GLfloat)tV; + tDst[4] = (GLfloat)tColor.x; + tDst[5] = (GLfloat)tColor.y; + tDst[6] = (GLfloat)tColor.z; + tDst[7] = (GLfloat)tAlpha; + tDst[8] = (GLfloat)tColorOffset.x; + tDst[9] = (GLfloat)tColorOffset.y; + tDst[10] = (GLfloat)tColorOffset.z; +} + +// tSrcRect in relative coords to texturesize, tDstRect in pixels +static void drawOpenGLTextureUniversal(int tTextureID, int tPaletteID, const GeoRectangle2D& tSrcRect, const Position2D& tTopLeft, const Position2D& tTopRight, const Position2D& tBottomLeft, const Position2D& tBottomRight, const DrawingData* tData, ShaderBlendType tShaderBlendType, int tHasPalette) { + const auto& shader = getActivePrismShaderReference(); + const Matrix4D* finalMatrix = &tData->mTransformationMatrix; + + float matrix[4][4]; + for (int y = 0; y < 4; y++) { + for (int x = 0; x < 4; x++) { + matrix[y][x] = (float)finalMatrix->m[y][x]; + } + } + +#ifndef __EMSCRIPTEN__ + if (tShaderBlendType != SHADER_BLEND_TYPE_NORMAL) { + glMemoryBarrier(GL_ALL_BARRIER_BITS); // wait or we can't be sure fbo is up-to-date + } +#endif + + glUniformMatrix4fv(shader.mAttribLocationProjMtx, 1, GL_FALSE, &matrix[0][0]); + glUniform3i(shader.mAttribLocationScreenSizeBlendStyle, (GLsizei)gOpenGLData.mRealScreenSize.x, (GLsizei)gOpenGLData.mRealScreenSize.y, int(tShaderBlendType)); + glUniform2f(shader.mAttribLocationDestinationAlphaColorFactor, GLfloat(tData->mDestAlpha), GLfloat(tData->mColorFactor)); + glUniform3i(shader.mAttribLocationPaletteSolidityInversionUsed, (GLsizei)tHasPalette, (GLsizei)tData->mIsColorSolid, (GLsizei)tData->mIsColorInversed); + + GLfloat vertices[4 * 11]; + setSingleVertex(&vertices[0 * 11], tTopLeft, tSrcRect.mTopLeft.x, tSrcRect.mTopLeft.y, Vector3D(tData->r, tData->g, tData->b), tData->a, Vector3D(tData->rOffset, tData->gOffset, tData->bOffset)); + setSingleVertex(&vertices[1 * 11], tTopRight, tSrcRect.mBottomRight.x, tSrcRect.mTopLeft.y, Vector3D(tData->r, tData->g, tData->b), tData->a, Vector3D(tData->rOffset, tData->gOffset, tData->bOffset)); + setSingleVertex(&vertices[2 * 11], tBottomRight, tSrcRect.mBottomRight.x, tSrcRect.mBottomRight.y, Vector3D(tData->r, tData->g, tData->b), tData->a, Vector3D(tData->rOffset, tData->gOffset, tData->bOffset)); + setSingleVertex(&vertices[3 * 11], tBottomLeft, tSrcRect.mTopLeft.x, tSrcRect.mBottomRight.y, Vector3D(tData->r, tData->g, tData->b), tData->a, Vector3D(tData->rOffset, tData->gOffset, tData->bOffset)); + + int stride = sizeof(GLfloat) * 11; + glBufferData(GL_ARRAY_BUFFER, 4 * stride, vertices, GL_STREAM_DRAW); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, tTextureID); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, tHasPalette ? gPrismWindowsDrawingData.mPalettes[tPaletteID] : tTextureID); + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, gOpenGLData.mFBOColorAttachment); + glDrawElements(GL_TRIANGLES, (GLsizei)6, GL_UNSIGNED_INT, 0); + glActiveTexture(GL_TEXTURE0); +} + +static void drawSortedSprite(const DrawListSpriteElement* e) { + GeoRectangle2D srcRect; + if (e->mTexturePosition.topLeft.x < e->mTexturePosition.bottomRight.x) { + srcRect.mTopLeft.x = e->mTexturePosition.topLeft.x / (double)(e->mTexture.mTextureSize.x); + srcRect.mBottomRight.x = (e->mTexturePosition.bottomRight.x + 1) / (double)(e->mTexture.mTextureSize.x); + } + else { + srcRect.mTopLeft.x = (e->mTexturePosition.topLeft.x + 1) / (double)(e->mTexture.mTextureSize.x); + srcRect.mBottomRight.x = e->mTexturePosition.bottomRight.x / (double)(e->mTexture.mTextureSize.x); + } + + if (e->mTexturePosition.topLeft.y < e->mTexturePosition.bottomRight.y) { + srcRect.mTopLeft.y = e->mTexturePosition.topLeft.y / (double)(e->mTexture.mTextureSize.y); + srcRect.mBottomRight.y = (e->mTexturePosition.bottomRight.y + 1) / (double)(e->mTexture.mTextureSize.y); + } + else { + srcRect.mTopLeft.y = (e->mTexturePosition.topLeft.y + 1) / (double)(e->mTexture.mTextureSize.y); + srcRect.mBottomRight.y = e->mTexturePosition.bottomRight.y / (double)(e->mTexture.mTextureSize.y); + } + + Texture texture = (Texture)e->mTexture.mTexture->mData; + auto blendType = e->mData.mBlendType; + ShaderBlendType shaderBlendType = SHADER_BLEND_TYPE_NORMAL; + if (blendType == BLEND_TYPE_ADDITION) { + blendType = BLEND_TYPE_NORMAL; + shaderBlendType = SHADER_BLEND_TYPE_ADDITION; + } else if(blendType == BLEND_TYPE_SUBTRACTION) { + blendType = BLEND_TYPE_NORMAL; + shaderBlendType = SHADER_BLEND_TYPE_SUBTRACTION; + } + + switch (blendType) { + case BLEND_TYPE_ADDITION: + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA); + break; + case BLEND_TYPE_NORMAL: + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + break; + case BLEND_TYPE_SUBTRACTION: + glBlendEquation(gOpenGLData.mSubtractionEquation); + glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA); + break; + case BLEND_TYPE_ONE: + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_ONE, GL_ZERO); + break; + default: + logError("Unimplemented blend type"); + logErrorInteger(e->mData.mBlendType); + recoverFromError(); + break; + } + + drawOpenGLTextureUniversal(texture->mTexture, e->mTexture.mPaletteID, srcRect, e->mTopLeft, e->mTopRight, e->mBottomLeft, e->mBottomRight, &e->mData, shaderBlendType, e->mTexture.mHasPalette); +} + +static void drawOpenGLTexture(GLuint tTextureID, const GeoRectangle2D& tSrcRect, const GeoRectangle2D& tDstRect, const DrawingData* tData, ShaderBlendType tShaderBlendType) { + drawOpenGLTextureUniversal(tTextureID, 0, tSrcRect, + tDstRect.mTopLeft, + Position2D(tDstRect.mBottomRight.x, tDstRect.mTopLeft.y), + Position2D(tDstRect.mTopLeft.x, tDstRect.mBottomRight.y), + tDstRect.mBottomRight, + tData, tShaderBlendType, 0); +} + +static int isTextPositionEmpty(char tChar) { + return tChar == ' '; +} + +static void drawSortedTruetype(const DrawListTruetypeElement* e) { + int l = int(strlen(e->mText)); + if (!l) return; + + SDL_Color color; + color.a = 0xFF; + color.r = (Uint8)(0xFF * e->mColor.x); + color.g = (Uint8)(0xFF * e->mColor.y); + color.b = (Uint8)(0xFF * e->mColor.z); + + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + auto pos = e->mPos; + int i; + for (i = 0; i < l;) { + char text[1024]; + + int start = i; + int previousWordStart = 0; + int j; + for (j = 0; j < l - start; j++) { + int w, h; + text[j] = e->mText[start + j]; + text[j + 1] = '\0'; + TTF_SizeText(e->mFont, text, &w, &h); + if (isTextPositionEmpty(text[j])) previousWordStart = j; + + if (w > e->mTextBoxWidth && !isTextPositionEmpty(text[j]) && previousWordStart > 0) { + text[previousWordStart + 1] = '\0'; + break; + } + else if (j == l - start - 1) { + previousWordStart = j; + } + } + int end = start + previousWordStart; + + SDL_Surface* surface = TTF_RenderText_Blended(e->mFont, text, color); + + GeoRectangle2D noCulled; + noCulled.mTopLeft = pos; + noCulled.mBottomRight = pos + Position2D(surface->w, surface->h); + + GeoRectangle2D rect; + rect.mTopLeft = clampPositionToGeoRectangle(noCulled.mTopLeft, e->mDrawRectangle); + rect.mBottomRight = clampPositionToGeoRectangle(noCulled.mBottomRight, e->mDrawRectangle); + + const auto topLeftSrc = Position2D((rect.mTopLeft.x - noCulled.mTopLeft.x) / surface->w, (rect.mTopLeft.y - noCulled.mTopLeft.y) / surface->h); + const auto bottomRightSrc = Position2D((rect.mBottomRight.x - noCulled.mTopLeft.x) / surface->w, (rect.mBottomRight.y - noCulled.mTopLeft.y) / surface->h); + + GeoRectangle2D src = GeoRectangle2D(topLeftSrc.x, topLeftSrc.y, bottomRightSrc.x - topLeftSrc.x, bottomRightSrc.y - topLeftSrc.y); + pos.y += surface->h; + + // must be converted because otherwise WebGL freezes up + auto convertedSurface = SDL_ConvertSurfaceFormat(surface, SDL_PIXELFORMAT_RGBA32, 0); + SDL_FreeSurface(surface); + + GLint last_texture; + GLuint texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, convertedSurface->w, convertedSurface->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, convertedSurface->pixels); + glBindTexture(GL_TEXTURE_2D, last_texture); + SDL_FreeSurface(convertedSurface); + + drawOpenGLTexture(texture, src, rect, &e->mData, SHADER_BLEND_TYPE_NORMAL); + glDeleteTextures(1, &texture); + i = end + 1; + } +} + +static void drawSorted(const DrawListElement& tData) { + const DrawListElement* e = &tData; + + if (e->mType == DrawListElement::Type::DRAW_LIST_ELEMENT_TYPE_SPRITE) { + const auto sprite = e->asSpriteElement(); + drawSortedSprite(sprite); + } + else if (e->mType == DrawListElement::Type::DRAW_LIST_ELEMENT_TYPE_TRUETYPE) { + const auto sprite = e->asTruetypeElement(); + drawSortedTruetype(sprite); + } + else { + logError("Unrecognized draw type"); + logErrorInteger(e->mType); + recoverFromError(); + } +} + +static void drawFBOToScreen() { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + const auto srcRect = GeoRectangle2D(0, 1, 1, -1); + const auto sz = getScreenSize(); + const auto dstRect = GeoRectangle2D(0, 0, sz.x, sz.y); + setDrawingParametersToIdentity(); + drawOpenGLTexture(gOpenGLData.mFBOColorAttachment, srcRect, dstRect, &gPrismWindowsDrawingData, SHADER_BLEND_TYPE_NORMAL); + glBindFramebuffer(GL_FRAMEBUFFER, gOpenGLData.mFBO); +} + +static void stopDrawingBookkeeping() +{ + struct timespec counter; + clock_gettime(CLOCK_MONOTONIC, &counter); + gBookkeepingData.mDrawingEndTime = counter.tv_sec + (counter.tv_nsec / 1e9); +} + +void stopDrawing() { + setProfilingSectionMarkerCurrentFunction(); + + for (auto& drawElement : gDrawVector) { + drawSorted(drawElement); + } + clearDrawVector(); + + if (isImguiPrismActive()) + { + imguiPrismRenderEnd(); + } + drawFBOToScreen(); + + SDL_GL_SwapWindow(gSDLWindow); + + stopDrawingBookkeeping(); +} + +void waitForRendering() {} + +void waitForScreen() { + setProfilingSectionMarkerCurrentFunction(); + gBookkeepingData.mRealFramerate = 60; +} + +bool isSkippingDrawing() +{ + return gBookkeepingData.mIsSkippingNextFrameDrawCounter; +} + +void setDrawingFrameSkippingEnabled(bool tIsEnabled) { + gBookkeepingData.mIsFrameSkippingEnabled = tIsEnabled; + if (tIsEnabled) { + gBookkeepingData.mIsSkippingNextFrameDrawCounter = 0; + resetDrawingFrameStartTime(); + } +} +void resetDrawingFrameStartTime() { + struct timespec counter; + clock_gettime(CLOCK_MONOTONIC, &counter); + gBookkeepingData.mFrameStartTime = counter.tv_sec + (counter.tv_nsec / 1e9); + gBookkeepingData.mRealFrameStartTime = gBookkeepingData.mFrameStartTime; +} + +void updateDrawingFrameStartTime(double tTimeDelta) { + gBookkeepingData.mFrameStartTime += tTimeDelta; + gBookkeepingData.mRealFrameStartTime += tTimeDelta; +} + +extern void getRGBFromColor(Color tColor, double* tR, double* tG, double* tB); + +void drawMultilineText(const char* tText, const char* tFullText, const Position& tPosition, const Vector3D& tFontSize, Color tColor, const Vector3D& tBreakSize, const Vector3D& tTextBoxSize) { + int current = 0; + + setDrawingBaseColor(tColor); + + TextureData fontData = getFontTexture(); + Position pos = tPosition; + + while (tText[current] != '\0') { + FontCharacterData charData = getFontCharacterData(tText[current]); + + PrismRectangle tTexturePosition; + tTexturePosition.topLeft.x = (int)(fontData.mTextureSize.x*charData.mFilePositionX1); + tTexturePosition.topLeft.y = (int)(fontData.mTextureSize.y*charData.mFilePositionY1); + tTexturePosition.bottomRight.x = (int)(fontData.mTextureSize.x*charData.mFilePositionX2); + tTexturePosition.bottomRight.y = (int)(fontData.mTextureSize.y*charData.mFilePositionY2); + + double dx = (double)abs(tTexturePosition.bottomRight.x - tTexturePosition.topLeft.x); + double dy = (double)abs(tTexturePosition.bottomRight.y - tTexturePosition.topLeft.y); + Vector3D scale = Vector3D(1 / dx, 1 / dy, 1); + scaleDrawing3D(vecScale3D(tFontSize, scale), pos); + + drawSprite(fontData, pos, tTexturePosition); + + pos.x += tFontSize.x + tBreakSize.x; + current++; + + if (hasToLinebreak(tFullText, current, tPosition, pos, tFontSize, tBreakSize, tTextBoxSize)) { + pos.x = tPosition.x - (tFontSize.x + tBreakSize.x); + pos.y += tFontSize.y + tBreakSize.y; + } + } + + setDrawingParametersToIdentity(); +} + +void drawTruetypeText(const char * tText, TruetypeFont tFont, const Position& tPosition, const Vector3DI& tTextSize, const Vector3D& tColor, double tTextBoxWidth, const GeoRectangle2D& tDrawRectangle) +{ + DrawListTruetypeElement e; + strcpy(e.mText, tText); + e.mFont = (TTF_Font*)tFont; + e.mPos = tPosition.xy(); + e.mTextSize = tTextSize; + e.mColor = tColor; + e.mTextBoxWidth = tTextBoxWidth; + e.mDrawRectangle = tDrawRectangle; + e.mData = gPrismWindowsDrawingData; + e.mZ = tPosition.z; + + gDrawVector.insert(DrawListElement(e)); +} + +void scaleDrawing(double tFactor, const Position& tScalePosition) { + scaleDrawing3D(Vector3D(tFactor, tFactor, 1), tScalePosition); +} + +void scaleDrawing2D(const Vector2D& tFactor, const Position2D& tScalePosition) { + setProfilingSectionMarkerCurrentFunction(); + scaleDrawing3D(tFactor.xyz(1.0), tScalePosition.xyz(0.0)); +} + +void scaleDrawing3D(const Vector3D& tFactor, const Position& tScalePosition) { + setProfilingSectionMarkerCurrentFunction(); + gPrismWindowsDrawingData.mTransformationMatrix = matMult4D(gPrismWindowsDrawingData.mTransformationMatrix, createTranslationMatrix4D(tScalePosition)); + gPrismWindowsDrawingData.mTransformationMatrix = matMult4D(gPrismWindowsDrawingData.mTransformationMatrix, createScaleMatrix4D(Vector3D(tFactor.x, tFactor.y, tFactor.z))); + gPrismWindowsDrawingData.mTransformationMatrix = matMult4D(gPrismWindowsDrawingData.mTransformationMatrix, createTranslationMatrix4D(vecScale(tScalePosition, -1))); +} + +void setDrawingBaseColorOffsetAdvanced(double r, double g, double b) { + gPrismWindowsDrawingData.rOffset = r; + gPrismWindowsDrawingData.gOffset = g; + gPrismWindowsDrawingData.bOffset = b; +} + +void setDrawingBaseColor(Color tColor) { + getRGBFromColor(tColor, &gPrismWindowsDrawingData.r, &gPrismWindowsDrawingData.g, &gPrismWindowsDrawingData.b); +} + +void setDrawingBaseColorAdvanced(double r, double g, double b) { + gPrismWindowsDrawingData.r = r; + gPrismWindowsDrawingData.g = g; + gPrismWindowsDrawingData.b = b; +} + +void setDrawingColorSolidity(int tIsSolid) +{ + gPrismWindowsDrawingData.mIsColorSolid = tIsSolid; +} + +void setDrawingColorInversed(int tIsInversed) +{ + gPrismWindowsDrawingData.mIsColorInversed = tIsInversed; +} + +void setDrawingColorFactor(double tColorFactor) { + gPrismWindowsDrawingData.mColorFactor = tColorFactor; +} + +void setDrawingTransparency(double tAlpha) { + gPrismWindowsDrawingData.a = tAlpha; +} + +void setDrawingDestinationTransparency(double tAlpha) { + gPrismWindowsDrawingData.mDestAlpha = tAlpha; +} + +void setDrawingRotationZ(double tAngle, const Position2D& tPosition) { + setProfilingSectionMarkerCurrentFunction(); + setDrawingRotationZ(tAngle, tPosition.xyz(0.0)); +} + +void setDrawingRotationZ(double tAngle, const Position& tPosition) { + setProfilingSectionMarkerCurrentFunction(); + tAngle = (2 * M_PI - tAngle); + gPrismWindowsDrawingData.mTransformationMatrix = matMult4D(gPrismWindowsDrawingData.mTransformationMatrix, createTranslationMatrix4D(tPosition)); + gPrismWindowsDrawingData.mTransformationMatrix = matMult4D(gPrismWindowsDrawingData.mTransformationMatrix, createRotationZMatrix4D(tAngle)); + gPrismWindowsDrawingData.mTransformationMatrix = matMult4D(gPrismWindowsDrawingData.mTransformationMatrix, createTranslationMatrix4D(vecScale(tPosition, -1))); +} + +void setDrawingParametersToIdentity() { + setProfilingSectionMarkerCurrentFunction(); + setDrawingBaseColor(COLOR_WHITE); + setDrawingTransparency(1.0); + setDrawingDestinationTransparency(1.0); + setDrawingBlendType(BLEND_TYPE_NORMAL); + setDrawingColorSolidity(0); + setDrawingColorInversed(0); + setDrawingColorFactor(1.0); + + ScreenSize sz = getScreenSize(); + Vector3D realScreenSize = Vector3D(sz.x*gOpenGLData.mScreenScale.x, sz.y*gOpenGLData.mScreenScale.y, 0); + gPrismWindowsDrawingData.mTransformationMatrix = createOrthographicProjectionMatrix4D(0, realScreenSize.x, 0, realScreenSize.y, 0, 100); + gPrismWindowsDrawingData.mTransformationMatrix = matMult4D(gPrismWindowsDrawingData.mTransformationMatrix, createTranslationMatrix4D(Vector3D(0, realScreenSize.y - gOpenGLData.mScreenScale.y*sz.y, 0))); + gPrismWindowsDrawingData.mTransformationMatrix = matMult4D(gPrismWindowsDrawingData.mTransformationMatrix, createScaleMatrix4D(Vector3D(gOpenGLData.mScreenScale.x, gOpenGLData.mScreenScale.y, 1))); +} + +void setDrawingBlendType(BlendType tBlendType) +{ + gPrismWindowsDrawingData.mBlendType = tBlendType; +} + +typedef struct { + double mAngle; + Position mCenter; +} RotationZEffect; + +typedef struct { + Vector3D mTranslation; + +} TranslationEffect; + +void pushDrawingTranslation(const Vector3D& tTranslation) { + + gPrismWindowsDrawingData.mTransformationMatrix = matMult4D(gPrismWindowsDrawingData.mTransformationMatrix, createTranslationMatrix4D(tTranslation)); + + TranslationEffect* e = (TranslationEffect*)allocMemory(sizeof(TranslationEffect)); + e->mTranslation = tTranslation; + vector_push_back_owned(&gPrismWindowsDrawingData.mEffectStack, e); +} +void pushDrawingRotationZ(double tAngle, const Vector3D& tCenter) { + setDrawingRotationZ(tAngle, tCenter); + + RotationZEffect* e = (RotationZEffect*)allocMemory(sizeof(RotationZEffect)); + e->mAngle = tAngle; + e->mCenter = tCenter; + vector_push_back_owned(&gPrismWindowsDrawingData.mEffectStack, e); +} + +void popDrawingRotationZ() { + int ind = vector_size(&gPrismWindowsDrawingData.mEffectStack) - 1; + RotationZEffect* e = (RotationZEffect*)vector_get(&gPrismWindowsDrawingData.mEffectStack, ind); + + setDrawingRotationZ(-e->mAngle, e->mCenter); + + vector_remove(&gPrismWindowsDrawingData.mEffectStack, ind); +} +void popDrawingTranslation() { + int ind = vector_size(&gPrismWindowsDrawingData.mEffectStack) - 1; + TranslationEffect* e = (TranslationEffect*)vector_get(&gPrismWindowsDrawingData.mEffectStack, ind); + + gPrismWindowsDrawingData.mTransformationMatrix = matMult4D(gPrismWindowsDrawingData.mTransformationMatrix, createTranslationMatrix4D(vecScale(e->mTranslation, -1))); + + vector_remove(&gPrismWindowsDrawingData.mEffectStack, ind); +} + +void disableDrawing() { + gPrismWindowsDrawingData.mIsDisabled = 1; +} + +void enableDrawing() { + gPrismWindowsDrawingData.mIsDisabled = 0; +} + +void setDrawingScreenScale(double tScaleX, double tScaleY) { + + gOpenGLData.mScreenScale = Vector3D(tScaleX, tScaleY, 1); + + ScreenSize sz = getScreenSize(); + gOpenGLData.mRealScreenSize = Vector3D(sz.x*gOpenGLData.mScreenScale.x, sz.y*gOpenGLData.mScreenScale.y, 0); + glViewport(0, 0, (GLsizei)gOpenGLData.mRealScreenSize.x, (GLsizei)gOpenGLData.mRealScreenSize.y); + recreateFBOs(); +} + +void setPaletteFromARGB256Buffer(int tPaletteID, const Buffer& tBuffer) { + assert(tBuffer.mLength == 256 * 4); + + uint8_t* src = (uint8_t*)tBuffer.mData; + std::vector finalBuffer(256 * 4); + for (int i = 0; i < 256; i++) { + finalBuffer[4 * i + 0] = src[4 * i + 1]; + finalBuffer[4 * i + 1] = src[4 * i + 2]; + finalBuffer[4 * i + 2] = src[4 * i + 3]; + finalBuffer[4 * i + 3] = src[4 * i + 0]; + } + + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glBindTexture(GL_TEXTURE_2D, gPrismWindowsDrawingData.mPalettes[tPaletteID]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, finalBuffer.data()); + glBindTexture(GL_TEXTURE_2D, last_texture); +} + +void setPaletteFromBGR256WithFirstValueTransparentBuffer(int tPaletteID, const Buffer& tBuffer) +{ + assert(tBuffer.mLength == 256 * 3); + + uint8_t* src = (uint8_t*)tBuffer.mData; + std::vector finalBuffer(256 * 4); + for (int i = 0; i < 256; i++) { + finalBuffer[4 * i + 0] = src[3 * i + 0]; + finalBuffer[4 * i + 1] = src[3 * i + 1]; + finalBuffer[4 * i + 2] = src[3 * i + 2]; + finalBuffer[4 * i + 3] = i == 0 ? 0 : 255; + } + + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glBindTexture(GL_TEXTURE_2D, gPrismWindowsDrawingData.mPalettes[tPaletteID]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, finalBuffer.data()); + glBindTexture(GL_TEXTURE_2D, last_texture); +} + +double getRealFramerate() { + return gBookkeepingData.mRealFramerate; +} +} diff --git a/linux/file_linux.cpp b/linux/file_linux.cpp new file mode 100644 index 0000000..83131b9 --- /dev/null +++ b/linux/file_linux.cpp @@ -0,0 +1,291 @@ +#include "prism/file.h" + +#include +#include + +#include "prism/log.h" +#include "prism/memoryhandler.h" +#include "prism/system.h" +#include "prism/debug.h" +#include "prism/datastructures.h" +#include "prism/windows/romdisk_win.h" + +#ifdef _WIN32 +#include +#include "prism/windows/debugimgui_win.h" +#endif + +extern char romdisk_buffer[]; +extern int romdisk_buffer_length; + +namespace prism { + + static struct { + char cwd[1024]; + char mFileSystem[1024]; + int mIsUsingRomdisk; + } gPrismWindowsFileData; + +#ifdef _WIN32 + void imguiFileHardware() + { + static bool isWindowShown = false; + imguiPrismAddTab("Prism", "File Hardware", &isWindowShown); + if (isWindowShown) + { + ImGui::Begin("File Hardware", &isWindowShown); + ImGui::Text("cwd = %s", gPrismWindowsFileData.cwd); + ImGui::Text("FileSystem = %s", gPrismWindowsFileData.mFileSystem); + ImGui::Text("IsUsingRomdisk = %d", gPrismWindowsFileData.mIsUsingRomdisk); + ImGui::End(); + } + } +#endif + + void initFileSystem() { + setActiveFileSystemOnStartup(); + sprintf(gPrismWindowsFileData.cwd, "/"); + sprintf(gPrismWindowsFileData.mFileSystem, "."); + debugString(gPrismWindowsFileData.cwd); + + initRomdisks(); + + if (romdisk_buffer_length) { + mountRomdiskFromBuffer(makeBuffer(romdisk_buffer, romdisk_buffer_length), "ASSETS"); + strcpy(gPrismWindowsFileData.mFileSystem, "/ASSETS"); + } + else if (isFile("assets.pak")) { + mountRomdisk("assets.pak", "ASSETS"); + strcpy(gPrismWindowsFileData.mFileSystem, "/ASSETS"); + } + } + + void shutdownFileSystem() { + shutdownRomdisks(); + } + + static void expandPath(char* tDest, const char* tPath) { + strcpy(tDest, tPath); + if (tDest[0] != '/') return; + + char potentialMount[1024]; + strcpy(potentialMount, tDest + 1); + char* endPos = strchr(potentialMount, '/'); + if (endPos != NULL) *endPos = '\0'; + + if (!strcmp("rd", potentialMount) || !strcmp("pc", potentialMount)) { + if (endPos == NULL) strcpy(tDest, "/"); + else sprintf(tDest, "/%s", endPos + 1); + return; + } + } + + void setFileSystem(const char* path) { + (void)path; + } + + const char* getFileSystem() { + return gPrismWindowsFileData.mFileSystem; + } + + + void setWorkingDirectory(const char* path) { + char expandedPath[1024], absolutePath[1024]; + if (path[0] != '/') { + sprintf(absolutePath, "%s%s", gPrismWindowsFileData.cwd, path); + } + else { + strcpy(absolutePath, path); + } + + expandPath(expandedPath, absolutePath); + strcpy(gPrismWindowsFileData.cwd, expandedPath); + debugString(gPrismWindowsFileData.cwd); + + int l = int(strlen(gPrismWindowsFileData.cwd)); + if (gPrismWindowsFileData.cwd[l - 1] != '/') { + gPrismWindowsFileData.cwd[l] = '/'; + gPrismWindowsFileData.cwd[l + 1] = '\0'; + } + } + + const char* getWorkingDirectory() { + return gPrismWindowsFileData.cwd; + } + + static int isAbsoluteWindowsDirectory(const char* tPath) { + return tPath[1] == ':'; + } + + void getFullPath(char* tDest, const char* tPath) { + if (isRomdiskPath(tPath) || isAbsoluteWindowsDirectory(tPath)) { + if (tPath[0] == '$') tPath++; + strcpy(tDest, tPath); + return; + } + + if (tPath[0] == '$') tPath += 4; + + if (tPath[0] == '/') { + char expandedPath[1024]; + expandPath(expandedPath, tPath); + sprintf(tDest, "%s%s", gPrismWindowsFileData.mFileSystem, expandedPath); + } + else sprintf(tDest, "%s%s%s", gPrismWindowsFileData.mFileSystem, gPrismWindowsFileData.cwd, tPath); + } + + FileHandler fileOpen(const char* tPath, int tFlags) { + + char path[1024]; + getFullPath(path, tPath); + + if (isRomdiskPath(path)) { + return fileOpenRomdisk(path, tFlags); + } + + verboseLog("Open file."); + verboseString(tPath); + verboseString(gPrismWindowsFileData.mFileSystem); + verboseString(gPrismWindowsFileData.cwd); + + char flags[100]; + flags[0] = '\0'; + if (tFlags == O_RDONLY) { + sprintf(flags, "rb"); + } + else if (tFlags == O_WRONLY) { + sprintf(flags, "wb+"); + } + else { + logError("Unrecognized read mode"); + logErrorInteger(tFlags) + recoverFromError(); + } + + if (isInDevelopMode() && isDebugMinusCheckEnabled() && strchr(path, '-')) { + logErrorFormat("Illegal character '-' in path %s. Aborting.", path); + recoverFromError(); + } + + return fopen(path, flags); + } + + int fileClose(FileHandler tHandler) { + if (isRomdiskFileHandler(tHandler)) return fileCloseRomdisk(tHandler); + + return fclose(tHandler); + } + size_t fileRead(FileHandler tHandler, void* tBuffer, size_t tCount) { + if (isRomdiskFileHandler(tHandler)) return fileReadRomdisk(tHandler, tBuffer, tCount); + + return fread(tBuffer, 1, tCount, tHandler); + } + size_t fileWrite(FileHandler tHandler, const void* tBuffer, size_t tCount) { + if (isRomdiskFileHandler(tHandler)) { + logError("Unable to write to romdisk file."); + logErrorPointer(tHandler); + recoverFromError(); + } + + return fwrite(tBuffer, 1, tCount, tHandler); + } + size_t fileSeek(FileHandler tHandler, size_t tOffset, int tWhence) { + if (isRomdiskFileHandler(tHandler)) return fileSeekRomdisk(tHandler, tOffset, tWhence); + + return fseek(tHandler, long(tOffset), tWhence); + } + size_t fileTell(FileHandler tHandler) { + if (isRomdiskFileHandler(tHandler)) return fileTellRomdisk(tHandler); + + return ftell(tHandler); + } + size_t fileTotal(FileHandler tHandler) { + if (isRomdiskFileHandler(tHandler)) return fileTotalRomdisk(tHandler); + + fseek(tHandler, 0L, SEEK_END); + size_t size = ftell(tHandler); + rewind(tHandler); + + return size; + } + + void fileFlush(FileHandler tHandler) { + if (isRomdiskFileHandler(tHandler)) return; + fflush(tHandler); + } + + int fileUnlink(const char* tPath) { + return remove(tPath); + } + + void* fileMemoryMap(FileHandler tHandler) { + (void)tHandler; + return NULL; + } + + void createDirectory(const char* tPath) + { +#ifdef _WIN32 + if (!isDirectory(tPath)) { + char path[1024]; + getFullPath(path, tPath); + std::filesystem::create_directories(path); + } +#endif + } + + void mountRomdiskFromBuffer(const Buffer& b, const char* tMountPath) + { + mountRomdiskWindowsFromBuffer(b, tMountPath); + } + + void mountRomdisk(const char* tFilePath, const char* tMountPath) { + + char fullPath[1024]; + expandPath(fullPath, tFilePath); + mountRomdiskWindows(fullPath, tMountPath); + } + + void unmountRomdisk(const char* tMountPath) { + unmountRomdiskWindows(tMountPath); + } + +#ifdef _WIN32 +#include +#endif + + void printDirectory(const char* tPath) { +#ifdef _WIN32 + char path[1024]; + wchar_t wpath[1024]; + getFullPath(path, tPath); + + WIN32_FIND_DATA findFileData; + HANDLE hFind; + + mbstowcs(wpath, path, 1024); + + hFind = FindFirstFile(wpath, &findFileData); + if (hFind == INVALID_HANDLE_VALUE) + { + int err = GetLastError(); + if (err == ERROR_FILE_NOT_FOUND) { + logg("No files in directory."); + logString(path); + return; + } + else { + logError("Unable to read directory"); + logErrorString(path); + recoverFromError(); + } + } + + do { + logWString(findFileData.cFileName); + } while (FindNextFile(hFind, &findFileData)); + + FindClose(hFind); +#endif + } +} \ No newline at end of file diff --git a/linux/framerateselect_linux.cpp b/linux/framerateselect_linux.cpp new file mode 100644 index 0000000..aadf0c5 --- /dev/null +++ b/linux/framerateselect_linux.cpp @@ -0,0 +1,9 @@ +#include "prism/framerateselectscreen.h" + +namespace prism { + + FramerateSelectReturnType selectFramerate() { + return FRAMERATE_SCREEN_RETURN_NORMAL; + } + +} \ No newline at end of file diff --git a/linux/input_linux.cpp b/linux/input_linux.cpp new file mode 100644 index 0000000..dbc748b --- /dev/null +++ b/linux/input_linux.cpp @@ -0,0 +1,970 @@ +#include "prism/input.h" + +#include +#include +#include + +#ifdef _WIN32 +#include +#include "prism/windows/debugimgui_win.h" +#endif + +#include + +#include "prism/log.h" +#include "prism/math.h" +#include "prism/clipboardhandler.h" +#include "prism/netplay.h" + +using namespace std; +namespace prism { + + typedef struct { + int mIsUsingController; + SDL_GameController* mController; + SDL_Haptic* mHaptic; + + int mIsRumbling; + SDL_HapticEffect mHapticEffect; + int mHapticEffectID; + Duration mRumbleNow; + Duration mRumbleDuration; + + std::deque> mButtonStates; // queue of CONTROLLER_BUTTON_AMOUNT_PRISM + } Controller; + + typedef struct { + int mIsActive; + void(*mCB)(void*, const std::string&); + void* mCaller; + } KeyInputWait; + + typedef struct + { + const Uint8* mKeyStatePointer; + std::deque> mKeyStates; // queue of SDL_NUM_SCANCODES + std::deque mConfirmationState; + } InputKeyboard; + + static struct { + KeyInputWait mInputWait; + + int mInputDelay; + int mInputBuffer; + + int mUsedKeyboard[MAXIMUM_CONTROLLER_AMOUNT]; + int mUsedKeyboardMapping[MAXIMUM_CONTROLLER_AMOUNT]; + InputKeyboard mKeyboards[MAXIMUM_CONTROLLER_AMOUNT]; + Controller mControllers[MAXIMUM_CONTROLLER_AMOUNT]; + } gPrismWindowsInputData; + +#ifdef _WIN32 + static void imguiUsedKeyboards() + { + if (ImGui::TreeNode("Used Keyboards")) + { + for (int i = 0; i < MAXIMUM_CONTROLLER_AMOUNT; i++) + { + ImGui::Text("Keyboard %d: %d", i, gPrismWindowsInputData.mUsedKeyboard[i]); + } + ImGui::TreePop(); + } + } + + static void imguiUsedKeyboardMappings() + { + if (ImGui::TreeNode("Used Keyboard Mappings")) + { + for (int i = 0; i < MAXIMUM_CONTROLLER_AMOUNT; i++) + { + ImGui::Text("Keyboard Mapping %d: %d", i, gPrismWindowsInputData.mUsedKeyboardMapping[i]); + } + ImGui::TreePop(); + } + } + + static void imguiKeyboardKeyStates(std::deque>& tKeyStates) + { + if (ImGui::TreeNode("Key States")) + { + for (auto& e : tKeyStates) + { + for (int i = 0; i < SDL_NUM_SCANCODES; i++) + { + if (e[i]) { + ImGui::Text("Key %d", i); + } + } + } + ImGui::TreePop(); + } + } + + static void imguiKeyboardComfirmationStates(std::deque& tConfirmationStates) + { + if (ImGui::TreeNode("Confirmation States")) + { + for (auto& e : tConfirmationStates) + { + ImGui::Text("Confirmation State: %d", e); + } + ImGui::TreePop(); + } + } + + static void imguiKeyboards() + { + if (ImGui::TreeNode("Keyboards")) + { + for (int i = 0; i < MAXIMUM_CONTROLLER_AMOUNT; i++) + { + ImGui::Text("Keyboard %d", i); + ImGui::Text("Key State Pointer: %p", gPrismWindowsInputData.mKeyboards[i].mKeyStatePointer); + imguiKeyboardKeyStates(gPrismWindowsInputData.mKeyboards[i].mKeyStates); + imguiKeyboardComfirmationStates(gPrismWindowsInputData.mKeyboards[i].mConfirmationState); + } + ImGui::TreePop(); + } + } + + static void imguiButtonStates(std::deque>& mButtonStates) + { + if (ImGui::TreeNode("Button States")) + { + for (auto& e : mButtonStates) + { + for (int i = 0; i < CONTROLLER_BUTTON_AMOUNT_PRISM; i++) + { + if (e[i]) { + ImGui::Text("Button %d", i); + } + } + } + ImGui::TreePop(); + } + } + + static void imguiControllers() + { + if (ImGui::TreeNode("Controllers")) + { + for (int i = 0; i < MAXIMUM_CONTROLLER_AMOUNT; i++) + { + ImGui::Text("Controller %d", i); + ImGui::Text("Is Using Controller: %d", gPrismWindowsInputData.mControllers[i].mIsUsingController); + if (gPrismWindowsInputData.mControllers[i].mIsUsingController) + { + ImGui::Text("Controller: %p", gPrismWindowsInputData.mControllers[i].mController); + ImGui::Text("Haptic: %p", gPrismWindowsInputData.mControllers[i].mHaptic); + ImGui::Text("Is Rumbling: %d", gPrismWindowsInputData.mControllers[i].mIsRumbling); + ImGui::Text("Rumble Now: %d", gPrismWindowsInputData.mControllers[i].mRumbleNow); + ImGui::Text("Rumble Duration: %d", gPrismWindowsInputData.mControllers[i].mRumbleDuration); + imguiButtonStates(gPrismWindowsInputData.mControllers[i].mButtonStates); + } + } + ImGui::TreePop(); + } + } + + static void imguiWindowsInputData() + { + imguiUsedKeyboards(); + imguiUsedKeyboardMappings(); + imguiKeyboards(); + imguiControllers(); + + ImGui::Text("Input Delay: %d", gPrismWindowsInputData.mInputDelay); + ImGui::Text("Input Buffer: %d", gPrismWindowsInputData.mInputBuffer); + ImGui::Text("Input Wait Active: %d", gPrismWindowsInputData.mInputWait.mIsActive); + } + + void imguiInputHardware() { + static bool isWindowShown = false; + imguiPrismAddTab("Prism", "Input HW", &isWindowShown); + if (isWindowShown) + { + ImGui::Begin("Input HW", &isWindowShown); + imguiWindowsInputData(); + ImGui::End(); + } + } +#endif + + static int evaluateSDLButtonA(int i) { + if (!gPrismWindowsInputData.mControllers[i].mIsUsingController) return 0; + return SDL_GameControllerGetButton(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_BUTTON_A); + } + + static int evaluateSDLButtonB(int i) { + if (!gPrismWindowsInputData.mControllers[i].mIsUsingController) return 0; + return SDL_GameControllerGetButton(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_BUTTON_B); + } + + static int evaluateSDLButtonX(int i) { + if (!gPrismWindowsInputData.mControllers[i].mIsUsingController) return 0; + return SDL_GameControllerGetButton(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_BUTTON_X); + } + + static int evaluateSDLButtonY(int i) { + if (!gPrismWindowsInputData.mControllers[i].mIsUsingController) return 0; + return SDL_GameControllerGetButton(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_BUTTON_Y); + } + +#ifdef VITA + static int evaluateSDLButtonL(int i) { + if (!gPrismWindowsInputData.mControllers[i].mIsUsingController) return 0; + return SDL_GameControllerGetButton(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_BUTTON_LEFTSHOULDER); + } + + static int evaluateSDLButtonR(int i) { + if (!gPrismWindowsInputData.mControllers[i].mIsUsingController) return 0; + return SDL_GameControllerGetButton(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER); + } +#else + static int evaluateSDLButtonL(int i) { + if (!gPrismWindowsInputData.mControllers[i].mIsUsingController) return 0; + double axis = SDL_GameControllerGetAxis(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_AXIS_TRIGGERLEFT) / 32767.0; + return (axis > 0.5); + } + + static int evaluateSDLButtonR(int i) { + if (!gPrismWindowsInputData.mControllers[i].mIsUsingController) return 0; + double axis = SDL_GameControllerGetAxis(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) / 32767.0; + return (axis > 0.5); + } +#endif + + static int evaluateSDLButtonLeft(int i) { + if (!gPrismWindowsInputData.mControllers[i].mIsUsingController) return 0; + double axis = SDL_GameControllerGetAxis(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_AXIS_LEFTX) / 32767.0; + int ret = (axis < -0.5); + ret |= SDL_GameControllerGetButton(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_BUTTON_DPAD_LEFT); + return ret; + } + + static int evaluateSDLButtonRight(int i) { + if (!gPrismWindowsInputData.mControllers[i].mIsUsingController) return 0; + double axis = SDL_GameControllerGetAxis(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_AXIS_LEFTX) / 32767.0; + int ret = (axis > 0.5); + ret |= SDL_GameControllerGetButton(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_BUTTON_DPAD_RIGHT); + return ret; + } + + static int evaluateSDLButtonUp(int i) { + if (!gPrismWindowsInputData.mControllers[i].mIsUsingController) return 0; + double axis = SDL_GameControllerGetAxis(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_AXIS_LEFTY) / 32767.0; + int ret = (axis < -0.5); + ret |= SDL_GameControllerGetButton(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_BUTTON_DPAD_UP); + return ret; + } + + static int evaluateSDLButtonDown(int i) { + if (!gPrismWindowsInputData.mControllers[i].mIsUsingController) return 0; + double axis = SDL_GameControllerGetAxis(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_AXIS_LEFTY) / 32767.0; + int ret = (axis > 0.5); + ret |= SDL_GameControllerGetButton(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_BUTTON_DPAD_DOWN); + return ret; + } + + static int evaluateSDLButtonStart(int i) { + if (!gPrismWindowsInputData.mControllers[i].mIsUsingController) return 0; + return SDL_GameControllerGetButton(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_BUTTON_START); + } + + typedef int(*InputEvaluationFunction)(int); + + static InputEvaluationFunction gSDLButtonMapping[] = { + evaluateSDLButtonA, + evaluateSDLButtonB, + evaluateSDLButtonX, + evaluateSDLButtonY, + evaluateSDLButtonL, + evaluateSDLButtonR, + evaluateSDLButtonLeft, + evaluateSDLButtonRight, + evaluateSDLButtonUp, + evaluateSDLButtonDown, + evaluateSDLButtonStart, + }; + + static ControllerButtonPrism gButtonMapping[MAXIMUM_CONTROLLER_AMOUNT][CONTROLLER_BUTTON_AMOUNT_PRISM] = { + { + CONTROLLER_A_PRISM, + CONTROLLER_B_PRISM, + CONTROLLER_X_PRISM, + CONTROLLER_Y_PRISM, + CONTROLLER_L_PRISM, + CONTROLLER_R_PRISM, + CONTROLLER_LEFT_PRISM, + CONTROLLER_RIGHT_PRISM, + CONTROLLER_UP_PRISM, + CONTROLLER_DOWN_PRISM, + CONTROLLER_START_PRISM, + }, + { + CONTROLLER_A_PRISM, + CONTROLLER_B_PRISM, + CONTROLLER_X_PRISM, + CONTROLLER_Y_PRISM, + CONTROLLER_L_PRISM, + CONTROLLER_R_PRISM, + CONTROLLER_LEFT_PRISM, + CONTROLLER_RIGHT_PRISM, + CONTROLLER_UP_PRISM, + CONTROLLER_DOWN_PRISM, + CONTROLLER_START_PRISM, + }, + }; + + + + static std::pair gPrismToSDLKeyboardMapping[] = { + make_pair(SDLK_a, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_b, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_c, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_d, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_e, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_f, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_g, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_h, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_i, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_j, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_k, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_l, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_m , SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_n, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_o, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_p, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_q, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_r, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_s, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_t, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_u, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_v, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_w, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_x, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_y, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_z, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_0, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_1, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_2, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_3, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_4, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_5, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_6, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_7, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_8, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_9, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_SPACE, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_LEFT, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_RIGHT, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_UP, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_DOWN, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_F1, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_F2, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_F3, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_F4, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_F5, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_F6, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_F7, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_F8, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_F9, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_F10, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_F11, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_F12, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_SCROLLLOCK, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_PAUSE, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_CARET, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_LCTRL, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_LALT, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_LSHIFT, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_RETURN, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_BACKSPACE, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_DELETE, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_PERIOD, SDL_SCANCODE_UNKNOWN), + make_pair(SDLK_SLASH, SDL_SCANCODE_SLASH), + }; + + static KeyboardKeyPrism gKeys[MAXIMUM_CONTROLLER_AMOUNT][KEYBOARD_AMOUNT_PRISM] = { + { + KEYBOARD_A_PRISM, + KEYBOARD_S_PRISM, + KEYBOARD_Q_PRISM, + KEYBOARD_W_PRISM, + KEYBOARD_E_PRISM, + KEYBOARD_D_PRISM, + KEYBOARD_LEFT_PRISM, + KEYBOARD_RIGHT_PRISM, + KEYBOARD_UP_PRISM, + KEYBOARD_DOWN_PRISM, + KEYBOARD_RETURN_PRISM, + }, + { + KEYBOARD_H_PRISM, + KEYBOARD_J_PRISM, + KEYBOARD_Y_PRISM, + KEYBOARD_U_PRISM, + KEYBOARD_I_PRISM, + KEYBOARD_K_PRISM, + KEYBOARD_4_PRISM, + KEYBOARD_6_PRISM, + KEYBOARD_8_PRISM, + KEYBOARD_2_PRISM, + KEYBOARD_3_PRISM, + }, + }; + + static void initKeyboardScancodes() { + for (int i = 0; i < KEYBOARD_AMOUNT_PRISM; i++) { + auto scancode = SDL_GetScancodeFromKey(gPrismToSDLKeyboardMapping[i].first); + if (scancode != SDL_SCANCODE_UNKNOWN) gPrismToSDLKeyboardMapping[i].second = scancode; + } + } + + static std::vector& getButtonStates(int i, int tNegativeFrameDelta) + { + return *(gPrismWindowsInputData.mControllers[i].mButtonStates.rbegin() - tNegativeFrameDelta); + } + + static std::vector& getKeyStates(int i, int tNegativeFrameDelta) + { + return *(gPrismWindowsInputData.mKeyboards[i].mKeyStates.rbegin() - tNegativeFrameDelta); + } + + static void updateKeyStateArraySize(int i) + { + while (gPrismWindowsInputData.mKeyboards[i].mKeyStates.size() < gPrismWindowsInputData.mInputDelay + gPrismWindowsInputData.mInputBuffer) + { + gPrismWindowsInputData.mKeyboards[i].mKeyStates.push_front(std::vector(SDL_NUM_SCANCODES, 0)); + gPrismWindowsInputData.mKeyboards[i].mConfirmationState.push_front(0); + } + + while (gPrismWindowsInputData.mKeyboards[i].mKeyStates.size() > gPrismWindowsInputData.mInputDelay + gPrismWindowsInputData.mInputBuffer) + { + gPrismWindowsInputData.mKeyboards[i].mKeyStates.pop_front(); + gPrismWindowsInputData.mKeyboards[i].mConfirmationState.pop_front(); + } + } + + static void updateKeyStateArraySizes() + { + for (int i = 0; i < MAXIMUM_CONTROLLER_AMOUNT; i++) { + updateKeyStateArraySize(i); + } + } + + static void updateControllerButtonStateArraySize(int i) + { + while (gPrismWindowsInputData.mControllers[i].mButtonStates.size() < gPrismWindowsInputData.mInputDelay + gPrismWindowsInputData.mInputBuffer) + { + gPrismWindowsInputData.mControllers[i].mButtonStates.push_front(std::vector(CONTROLLER_BUTTON_AMOUNT_PRISM, 0)); + } + + while (gPrismWindowsInputData.mControllers[i].mButtonStates.size() > gPrismWindowsInputData.mInputDelay + gPrismWindowsInputData.mInputBuffer) + { + gPrismWindowsInputData.mControllers[i].mButtonStates.pop_front(); + } + } + + static void updateControllerButtonStateArraySizes() + { + for (int i = 0; i < MAXIMUM_CONTROLLER_AMOUNT; i++) { + updateControllerButtonStateArraySize(i); + } + } + + void initInput() { + gPrismWindowsInputData.mInputWait.mIsActive = 0; + + gPrismWindowsInputData.mUsedKeyboard[PRISM_KEYBOARD_LOCAL] = gPrismWindowsInputData.mUsedKeyboard[PRISM_KEYBOARD_NETPLAY] = 0; + for (int i = 0; i < MAXIMUM_CONTROLLER_AMOUNT; i++) { + gPrismWindowsInputData.mUsedKeyboardMapping[i] = i; + } + gPrismWindowsInputData.mKeyboards[PRISM_KEYBOARD_LOCAL].mKeyStatePointer = SDL_GetKeyboardState(NULL); + gPrismWindowsInputData.mInputDelay = 0; + gPrismWindowsInputData.mInputBuffer = 2; + updateKeyStateArraySizes(); + updateControllerButtonStateArraySizes(); + initKeyboardScancodes(); + } + + static void loadController(int i) { + if (gPrismWindowsInputData.mControllers[i].mIsUsingController) return; + + gPrismWindowsInputData.mControllers[i].mController = SDL_GameControllerOpen(i); + gPrismWindowsInputData.mControllers[i].mHaptic = SDL_HapticOpenFromJoystick(SDL_GameControllerGetJoystick(gPrismWindowsInputData.mControllers[i].mController)); + gPrismWindowsInputData.mControllers[i].mIsRumbling = 0; + gPrismWindowsInputData.mControllers[i].mIsUsingController = 1; + } + + static void unloadController(int i) { + if (!gPrismWindowsInputData.mControllers[i].mIsUsingController) return; + turnControllerRumbleOffSingle(i); + if (gPrismWindowsInputData.mControllers[i].mHaptic) SDL_HapticClose(gPrismWindowsInputData.mControllers[i].mHaptic); + SDL_GameControllerClose(gPrismWindowsInputData.mControllers[i].mController); + gPrismWindowsInputData.mControllers[i].mController = NULL; + gPrismWindowsInputData.mControllers[i].mIsUsingController = 0; + } + + static void fillSingleButtonState(int i, int buttonIndex, std::vector& data) { + data[buttonIndex] = uint8_t(gSDLButtonMapping[gButtonMapping[i][buttonIndex]](i)); + } + + static void updateControllerButtonStateArrayContentFillFrontWithCurrentButtonState(int i) { + auto& data = gPrismWindowsInputData.mControllers[i].mButtonStates.front(); + for (int buttonIndex = 0; buttonIndex < CONTROLLER_BUTTON_AMOUNT_PRISM; buttonIndex++) + { + fillSingleButtonState(i, buttonIndex, data); + } + } + + static void updateControllerButtonStateArrayContent(int i) { + updateControllerButtonStateArrayContentFillFrontWithCurrentButtonState(i); + gPrismWindowsInputData.mControllers[i].mButtonStates.push_back(gPrismWindowsInputData.mControllers[i].mButtonStates.front()); + gPrismWindowsInputData.mControllers[i].mButtonStates.pop_front(); + } + + static void updateControllerButtonStates(int i) + { + updateControllerButtonStateArraySize(i); + updateControllerButtonStateArrayContent(i); + } + + static void updateSingleControllerInput(int i) { + if (i >= SDL_NumJoysticks()) { + unloadController(i); + return; + } + + loadController(i); + updateControllerButtonStates(i); + } + + static void updateSingleControllerRumble(int i) { + if (!isUsingControllerSingle(i)) return; + if (!gPrismWindowsInputData.mControllers[i].mIsRumbling) return; + + if (handleDurationAndCheckIfOver(&gPrismWindowsInputData.mControllers[i].mRumbleNow, gPrismWindowsInputData.mControllers[i].mRumbleDuration)) { + turnControllerRumbleOffSingle(i); + } + } + + static void updateControllers() { + int i; + for (i = 0; i < MAXIMUM_CONTROLLER_AMOUNT; i++) { + updateSingleControllerInput(i); + updateSingleControllerRumble(i); + } + } + + static void updateKeyboardLocalKeyboard(int i) { + updateKeyStateArraySize(i); +#ifdef _WIN32 + auto& io = ImGui::GetIO(); + if (!io.WantTextInput) +#endif + { + memcpy(gPrismWindowsInputData.mKeyboards[i].mKeyStates.front().data(), gPrismWindowsInputData.mKeyboards[i].mKeyStatePointer, SDL_NUM_SCANCODES); + } + gPrismWindowsInputData.mKeyboards[i].mKeyStates.push_back(gPrismWindowsInputData.mKeyboards[i].mKeyStates.front()); + gPrismWindowsInputData.mKeyboards[i].mConfirmationState.push_back(1); + gPrismWindowsInputData.mKeyboards[i].mKeyStates.pop_front(); + gPrismWindowsInputData.mKeyboards[i].mConfirmationState.pop_front(); + } + + static void updateKeyboardRemoteKeyboard(int i) { + updateKeyStateArraySize(i); + gPrismWindowsInputData.mKeyboards[i].mKeyStates.push_back(std::vector(SDL_NUM_SCANCODES, 0)); + gPrismWindowsInputData.mKeyboards[i].mConfirmationState.push_back(0); + gPrismWindowsInputData.mKeyboards[i].mKeyStates.pop_front(); + gPrismWindowsInputData.mKeyboards[i].mConfirmationState.pop_front(); + } + + static void updateKeyboards() + { + if (isNetplaySyncing()) { + updateKeyboardRemoteKeyboard(PRISM_KEYBOARD_LOCAL); + } + else { + updateKeyboardLocalKeyboard(PRISM_KEYBOARD_LOCAL); + } + if (gPrismWindowsInputData.mUsedKeyboard[0] == PRISM_KEYBOARD_NETPLAY || gPrismWindowsInputData.mUsedKeyboard[1] == PRISM_KEYBOARD_NETPLAY) { + updateKeyboardRemoteKeyboard(PRISM_KEYBOARD_NETPLAY); + } + } + + void updateInputPlatform() { + updateKeyboards(); + updateControllers(); + } + + int hasPressedASingle(int i) { + int state = getKeyStates(gPrismWindowsInputData.mUsedKeyboard[i], -gPrismWindowsInputData.mInputDelay)[gPrismToSDLKeyboardMapping[gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][CONTROLLER_A_PRISM]].second]; + state |= getButtonStates(i, -gPrismWindowsInputData.mInputDelay)[CONTROLLER_A_PRISM]; + return state; + } + + int hasPressedBSingle(int i) { + int state = getKeyStates(gPrismWindowsInputData.mUsedKeyboard[i], -gPrismWindowsInputData.mInputDelay)[gPrismToSDLKeyboardMapping[gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][CONTROLLER_B_PRISM]].second]; + state |= getButtonStates(i, -gPrismWindowsInputData.mInputDelay)[CONTROLLER_B_PRISM]; + return state; + } + + int hasPressedXSingle(int i) { + int state = getKeyStates(gPrismWindowsInputData.mUsedKeyboard[i], -gPrismWindowsInputData.mInputDelay)[gPrismToSDLKeyboardMapping[gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][CONTROLLER_X_PRISM]].second]; + state |= getButtonStates(i, -gPrismWindowsInputData.mInputDelay)[CONTROLLER_X_PRISM]; + return state; + } + + int hasPressedYSingle(int i) { + int state = getKeyStates(gPrismWindowsInputData.mUsedKeyboard[i], -gPrismWindowsInputData.mInputDelay)[gPrismToSDLKeyboardMapping[gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][CONTROLLER_Y_PRISM]].second]; + state |= getButtonStates(i, -gPrismWindowsInputData.mInputDelay)[CONTROLLER_Y_PRISM]; + return state; + } + + int hasPressedLeftSingle(int i) { + int state = getKeyStates(gPrismWindowsInputData.mUsedKeyboard[i], -gPrismWindowsInputData.mInputDelay)[gPrismToSDLKeyboardMapping[gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][CONTROLLER_LEFT_PRISM]].second]; + state |= getButtonStates(i, -gPrismWindowsInputData.mInputDelay)[CONTROLLER_LEFT_PRISM]; + return state; + } + + int hasPressedRightSingle(int i) { + int state = getKeyStates(gPrismWindowsInputData.mUsedKeyboard[i], -gPrismWindowsInputData.mInputDelay)[gPrismToSDLKeyboardMapping[gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][CONTROLLER_RIGHT_PRISM]].second]; + state |= getButtonStates(i, -gPrismWindowsInputData.mInputDelay)[CONTROLLER_RIGHT_PRISM]; + return state; + } + + int hasPressedUpSingle(int i) { + int state = getKeyStates(gPrismWindowsInputData.mUsedKeyboard[i], -gPrismWindowsInputData.mInputDelay)[gPrismToSDLKeyboardMapping[gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][CONTROLLER_UP_PRISM]].second]; + state |= getButtonStates(i, -gPrismWindowsInputData.mInputDelay)[CONTROLLER_UP_PRISM]; + return state; + } + + int hasPressedDownSingle(int i) { + int state = getKeyStates(gPrismWindowsInputData.mUsedKeyboard[i], -gPrismWindowsInputData.mInputDelay)[gPrismToSDLKeyboardMapping[gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][CONTROLLER_DOWN_PRISM]].second]; + state |= getButtonStates(i, -gPrismWindowsInputData.mInputDelay)[CONTROLLER_DOWN_PRISM]; + return state; + } + + int hasPressedLSingle(int i) { + int state = getKeyStates(gPrismWindowsInputData.mUsedKeyboard[i], -gPrismWindowsInputData.mInputDelay)[gPrismToSDLKeyboardMapping[gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][CONTROLLER_L_PRISM]].second]; + state |= getButtonStates(i, -gPrismWindowsInputData.mInputDelay)[CONTROLLER_L_PRISM]; + return state; + } + + int hasPressedRSingle(int i) { + int state = getKeyStates(gPrismWindowsInputData.mUsedKeyboard[i], -gPrismWindowsInputData.mInputDelay)[gPrismToSDLKeyboardMapping[gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][CONTROLLER_R_PRISM]].second]; + state |= getButtonStates(i, -gPrismWindowsInputData.mInputDelay)[CONTROLLER_R_PRISM]; + return state; + } + + int hasPressedStartSingle(int i) { + int state = getKeyStates(gPrismWindowsInputData.mUsedKeyboard[i], -gPrismWindowsInputData.mInputDelay)[gPrismToSDLKeyboardMapping[gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][CONTROLLER_START_PRISM]].second]; + state |= getButtonStates(i, -gPrismWindowsInputData.mInputDelay)[CONTROLLER_START_PRISM]; + return state; + } + + int hasPressedAbortSingle(int i) { + int state = getKeyStates(gPrismWindowsInputData.mUsedKeyboard[i], -gPrismWindowsInputData.mInputDelay)[SDL_SCANCODE_ESCAPE]; + if (gPrismWindowsInputData.mControllers[i].mIsUsingController) { + state |= (hasPressedASingle(i) && hasPressedBSingle(i) && hasPressedXSingle(i) && hasPressedYSingle(i) && hasPressedStartSingle(i)); + } + return state; + } + + int hasShotGunSingle(int /*i*/) + { + uint32_t mask = SDL_GetMouseState(NULL, NULL); + return mask & SDL_BUTTON(SDL_BUTTON_LEFT); + } + + extern Vector3D correctSDLWindowPosition(const Vector3D& v); + + Vector3D getShotPositionSingle(int /*i*/) { + int x, y; + SDL_GetMouseState(&x, &y); + Vector3D ret = Vector3D(x, y, 0); + return correctSDLWindowPosition(ret); + } + + + static double getStickNormalizedBinary(int i, int tCodeMinus, int tCodePlus) { + if (getKeyStates(gPrismWindowsInputData.mUsedKeyboard[i], -gPrismWindowsInputData.mInputDelay)[tCodeMinus]) return -1; + else if (getKeyStates(gPrismWindowsInputData.mUsedKeyboard[i], -gPrismWindowsInputData.mInputDelay)[tCodePlus]) return 1; + else return 0; + } + + double getSingleLeftStickNormalizedX(int i) { + if (gPrismWindowsInputData.mControllers[i].mIsUsingController) { + return SDL_GameControllerGetAxis(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_AXIS_LEFTX) / 32767.0; + } + else return getStickNormalizedBinary(i, gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][CONTROLLER_LEFT_PRISM], gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][CONTROLLER_RIGHT_PRISM]); + } + + double getSingleLeftStickNormalizedY(int i) { + if (gPrismWindowsInputData.mControllers[i].mIsUsingController) { + return SDL_GameControllerGetAxis(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_AXIS_LEFTY) / 32767.0; + } + else return getStickNormalizedBinary(i, gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][CONTROLLER_UP_PRISM], gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][CONTROLLER_DOWN_PRISM]); + } + + double getSingleLNormalized(int i) { + if (gPrismWindowsInputData.mControllers[i].mIsUsingController) { + return SDL_GameControllerGetAxis(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_AXIS_TRIGGERLEFT) / 32767.0; + } + else return getKeyStates(gPrismWindowsInputData.mUsedKeyboard[i], -gPrismWindowsInputData.mInputDelay)[gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][CONTROLLER_L_PRISM]]; + } + + double getSingleRNormalized(int i) { + if (gPrismWindowsInputData.mControllers[i].mIsUsingController) { + return SDL_GameControllerGetAxis(gPrismWindowsInputData.mControllers[i].mController, SDL_CONTROLLER_AXIS_TRIGGERRIGHT) / 32767.0; + } + else return getKeyStates(gPrismWindowsInputData.mUsedKeyboard[i], -gPrismWindowsInputData.mInputDelay)[gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][CONTROLLER_R_PRISM]]; + } + + extern SDL_Window* gSDLWindow; + + void forceMouseCursorToWindow() { +#ifdef __EMSCRIPTEN__ + return; +#endif + SDL_SetWindowGrab(gSDLWindow, SDL_TRUE); + } + + void releaseMouseCursorFromWindow() { +#ifdef __EMSCRIPTEN__ + return; +#endif + SDL_SetWindowGrab(gSDLWindow, SDL_FALSE); + } + + int isUsingControllerSingle(int i) { + return gPrismWindowsInputData.mControllers[i].mIsUsingController; + } + + void addControllerRumbleSingle(int i, Duration tDuration, int tFrequency, double tAmplitude) { + if (!isUsingControllerSingle(i)) return; + if (!gPrismWindowsInputData.mControllers[i].mHaptic) return; + + turnControllerRumbleOffSingle(i); + + SDL_HapticEffect* effect = &gPrismWindowsInputData.mControllers[i].mHapticEffect; + memset(effect, 0, sizeof(SDL_HapticEffect)); + + if ((SDL_HapticQuery(gPrismWindowsInputData.mControllers[i].mHaptic) & SDL_HAPTIC_SINE)) { + effect->type = SDL_HAPTIC_SINE; + } + else { + effect->type = SDL_HAPTIC_LEFTRIGHT; + } + effect->periodic.direction.type = SDL_HAPTIC_POLAR; + effect->periodic.direction.dir[0] = 18000; + effect->periodic.period = (uint16_t)tFrequency; + + effect->periodic.magnitude = (int16_t)(INT16_MAX * fclamp(tAmplitude, 0, 1)); + effect->periodic.length = 5000; + effect->periodic.attack_length = 1000; + effect->periodic.fade_length = 1000; + + gPrismWindowsInputData.mControllers[i].mHapticEffectID = SDL_HapticNewEffect(gPrismWindowsInputData.mControllers[i].mHaptic, effect); + + SDL_HapticRunEffect(gPrismWindowsInputData.mControllers[i].mHaptic, gPrismWindowsInputData.mControllers[i].mHapticEffectID, 1); + + gPrismWindowsInputData.mControllers[i].mRumbleNow = 0; + gPrismWindowsInputData.mControllers[i].mRumbleDuration = tDuration; + gPrismWindowsInputData.mControllers[i].mIsRumbling = 1; + } + + void turnControllerRumbleOffSingle(int i) { + if (!gPrismWindowsInputData.mControllers[i].mIsRumbling) return; + + SDL_HapticDestroyEffect(gPrismWindowsInputData.mControllers[i].mHaptic, gPrismWindowsInputData.mControllers[i].mHapticEffectID); + gPrismWindowsInputData.mControllers[i].mIsRumbling = 0; + } + + int hasPressedRawButton(int i, ControllerButtonPrism tButton) { + return gSDLButtonMapping[tButton](i); + } + + int hasPressedRawKeyboardKey(KeyboardKeyPrism tKey) { + int id = gPrismToSDLKeyboardMapping[tKey].second; + return getKeyStates(PRISM_KEYBOARD_LOCAL, -gPrismWindowsInputData.mInputDelay)[id]; + } + + int hasPressedKeyboardKeyFlank(KeyboardKeyPrism tKey) { + int id = gPrismToSDLKeyboardMapping[tKey].second; + return !getKeyStates(PRISM_KEYBOARD_LOCAL, -gPrismWindowsInputData.mInputDelay - 1)[id] && getKeyStates(PRISM_KEYBOARD_LOCAL, -gPrismWindowsInputData.mInputDelay)[id]; + } + + int hasPressedKeyboardMultipleKeyFlank(int tKeyAmount, ...) { + if (!tKeyAmount) return 0; + + int i; + va_list vl; + va_start(vl, tKeyAmount); + + int previousKeyPressed = 1, currentKeyPressed = 1; + for (i = 0; i < tKeyAmount; i++) + { + KeyboardKeyPrism singleKey = (KeyboardKeyPrism)va_arg(vl, int); + int id = gPrismToSDLKeyboardMapping[singleKey].second; + previousKeyPressed = previousKeyPressed && getKeyStates(PRISM_KEYBOARD_LOCAL, -gPrismWindowsInputData.mInputDelay - 1)[id]; + currentKeyPressed = currentKeyPressed && getKeyStates(PRISM_KEYBOARD_LOCAL, -gPrismWindowsInputData.mInputDelay)[id]; + } + va_end(vl); + + return !previousKeyPressed && currentKeyPressed; + } + + ControllerButtonPrism getButtonForController(int i, ControllerButtonPrism tTargetButton) + { + return gButtonMapping[i][tTargetButton]; + } + + void setButtonForController(int i, ControllerButtonPrism tTargetButton, ControllerButtonPrism tButtonValue) + { + gButtonMapping[i][tTargetButton] = tButtonValue; + } + + KeyboardKeyPrism getButtonForKeyboard(int i, ControllerButtonPrism tTargetButton) + { + return gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][tTargetButton]; + } + + void setButtonForKeyboard(int i, ControllerButtonPrism tTargetButton, KeyboardKeyPrism tKeyValue) + { + gKeys[gPrismWindowsInputData.mUsedKeyboardMapping[i]][tTargetButton] = tKeyValue; + } + + void receiveCharacterInputFromSDL(const std::string& tText) { + if (!gPrismWindowsInputData.mInputWait.mIsActive) return; + gPrismWindowsInputData.mInputWait.mCB(gPrismWindowsInputData.mInputWait.mCaller, tText); + } + + void waitForCharacterFromUserInput(int /*i*/, void(*tCB)(void*, const std::string&), void* tCaller) { + SDL_StartTextInput(); + + gPrismWindowsInputData.mInputWait.mCB = tCB; + gPrismWindowsInputData.mInputWait.mCaller = tCaller; + gPrismWindowsInputData.mInputWait.mIsActive = 1; + } + + void cancelWaitingForCharacterFromUserInput(int /*i*/) { + SDL_StopTextInput(); + gPrismWindowsInputData.mInputWait.mIsActive = 0; + } + + int getInputDelay() + { + return gPrismWindowsInputData.mInputDelay; + } + + void setInputDelay(int tInputDelay) { + gPrismWindowsInputData.mInputDelay = tInputDelay; + updateKeyStateArraySizes(); + updateControllerButtonStateArraySizes(); + } + + void setInputBufferSize(int tInputBufferSize) + { + gPrismWindowsInputData.mInputBuffer = std::max(2, tInputBufferSize); + updateKeyStateArraySizes(); + updateControllerButtonStateArraySizes(); + } + + void setInputUsedKeyboardByPlayer(int i, int tKeyboardIndex) { + gPrismWindowsInputData.mUsedKeyboard[i] = tKeyboardIndex; + } + + void setInputUsedKeyboardMappingByPlayer(int i, int tMappingIndex) { + gPrismWindowsInputData.mUsedKeyboardMapping[i] = tMappingIndex; + } + + void gatherWindowsInputStateForPastFrames(int i, size_t pastFrames, std::vector>& tKeyStates, std::vector>& tButtonStates) { + size_t regularGatherFrames = std::min(pastFrames, gPrismWindowsInputData.mKeyboards[PRISM_KEYBOARD_LOCAL].mKeyStates.size()); + for (size_t frameIndex = 0; frameIndex < regularGatherFrames; frameIndex++) { + tKeyStates.push_back(*(gPrismWindowsInputData.mKeyboards[PRISM_KEYBOARD_LOCAL].mKeyStates.rbegin() + frameIndex)); + tButtonStates.push_back(*(gPrismWindowsInputData.mControllers[i].mButtonStates.rbegin() + frameIndex)); + } + + if (tKeyStates.size() < pastFrames) { + logWarningFormat("[Input] Unable to gather requested input frame count, availability only %d", regularGatherFrames); + while (tKeyStates.size() < pastFrames) { + tKeyStates.push_back(tKeyStates.back()); + tButtonStates.push_back(tButtonStates.back()); + } + } + } + + void gatherWindowsInputStateForLogging(std::vector& tConfirmationStates, std::vector>& tKeyStates, std::vector>& tButtonStates) { + for (int i = 0; i < 2; i++) + { + tKeyStates.push_back(*(gPrismWindowsInputData.mKeyboards[i].mKeyStates.rbegin())); + tButtonStates.push_back(*(gPrismWindowsInputData.mControllers[i].mButtonStates.rbegin())); + tConfirmationStates.push_back(*(gPrismWindowsInputData.mKeyboards[i].mConfirmationState.rbegin())); + } + } + + void setInputForFrameDelta(int i, int tNegativeFrameDelta, const std::vector>& tKeyStates, const std::vector>& tButtonStates) + { + int baseIndex = -tNegativeFrameDelta; + for (size_t frameIndex = 0; frameIndex < tKeyStates.size(); frameIndex++) { + if (baseIndex < 0) { + baseIndex++; + continue; + } + if (baseIndex >= gPrismWindowsInputData.mKeyboards[PRISM_KEYBOARD_NETPLAY].mKeyStates.size()) break; + auto& keyConfirmation = *(gPrismWindowsInputData.mKeyboards[PRISM_KEYBOARD_NETPLAY].mConfirmationState.rbegin() + baseIndex); + if (keyConfirmation) continue; + + auto& keyStates = *(gPrismWindowsInputData.mKeyboards[PRISM_KEYBOARD_NETPLAY].mKeyStates.rbegin() + baseIndex); + auto& buttonStates = *(gPrismWindowsInputData.mControllers[i].mButtonStates.rbegin() + baseIndex); + keyStates = tKeyStates[frameIndex]; + keyConfirmation = 1; + buttonStates = tButtonStates[frameIndex]; + baseIndex++; + } + } + + int isNetplayInputConfirmed() { + if (gPrismWindowsInputData.mInputDelay >= gPrismWindowsInputData.mKeyboards[PRISM_KEYBOARD_NETPLAY].mConfirmationState.size()) return 0; + const auto& confirmationState = *(gPrismWindowsInputData.mKeyboards[PRISM_KEYBOARD_NETPLAY].mConfirmationState.rbegin() + gPrismWindowsInputData.mInputDelay); + return confirmationState; + } + Vector2D getMousePointerPosition() + { + int x, y; + SDL_GetMouseState(&x, &y); + const auto sz = getScreenSize(); + const auto displaySize = getDisplayedScreenSize(); + const auto scaleX = sz.x / (double)displaySize.x; + const auto scaleY = sz.y / (double)displaySize.y; + return Vector2D(x * scaleX, y * scaleY); + } + + bool isMouseInRectangle(const GeoRectangle2D& tRectangle) + { + const auto pos = getMousePointerPosition(); + return pos.x >= tRectangle.mTopLeft.x && pos.x <= tRectangle.mBottomRight.x && pos.y >= tRectangle.mTopLeft.y && pos.y <= tRectangle.mBottomRight.y; + + } + bool hasPressedMouseLeft() + { + return hasPressedMouseLeftSingle(0); + } + + bool hasPressedMouseLeftSingle(int) + { + return SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_LEFT); + } + + bool hasPressedMouseRight() + { + return hasPressedMouseRightSingle(0); + } + + bool hasPressedMouseRightSingle(int) + { + return SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_RIGHT); + } +} \ No newline at end of file diff --git a/linux/log_linux.cpp b/linux/log_linux.cpp new file mode 100644 index 0000000..113563e --- /dev/null +++ b/linux/log_linux.cpp @@ -0,0 +1,84 @@ +#include "prism/log.h" + +#include + +#include + +#ifdef _WIN32 +#include +#include +#endif + +namespace prism { + + void logTextureMemoryState() { + // Not applicable / desirable / important under Windows + } + +#ifdef _WIN32 + void logMemoryState() { + if (!isInDevelopMode()) return; + PROCESS_MEMORY_COUNTERS memCounter; + if (GetProcessMemoryInfo(GetCurrentProcess(), &memCounter, sizeof(memCounter))) { + logFormat("malloc_stats: Working set size: %lu", memCounter.WorkingSetSize); + } + } + + void printLogColorStart(LogType tType) { + HANDLE console; + switch (tType) { + case LOG_TYPE_WARNING: + console = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(console, FOREGROUND_RED | FOREGROUND_GREEN); + break; + case LOG_TYPE_ERROR: + console = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(console, FOREGROUND_RED); + break; + default: + break; + } + } + + void printLogColorEnd(LogType tType) { + HANDLE console; + switch (tType) { + case LOG_TYPE_WARNING: + case LOG_TYPE_ERROR: + console = GetStdHandle(STD_OUTPUT_HANDLE); + SetConsoleTextAttribute(console, FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + break; + default: + break; + } + } + +#else + void logMemoryState() { + // total memory is known at compile time or dynamic + } + + // textarea used for emscripten output does not support different colors + void printLogColorStart(LogType /*tType*/) {} + void printLogColorEnd(LogType /*tType*/) {} +#endif + + void hardwareLogToFile(FileHandler& tFileHandler, const char* tText) { + if (!isInDevelopMode()) return; + if (isOnWeb()) return; + + auto prevLogType = getMinimumLogType(); + setMinimumLogType(LOG_TYPE_NONE); + + if (tFileHandler == FILEHND_INVALID) { + createDirectory("$pc/debug"); + tFileHandler = fileOpen("$pc/debug/log.txt", O_WRONLY); + } + if (tFileHandler == FILEHND_INVALID) return; + + fileWrite(tFileHandler, tText, strlen(tText)); + fileFlush(tFileHandler); + + setMinimumLogType(prevLogType); + } +} \ No newline at end of file diff --git a/linux/logoscreen_linux.cpp b/linux/logoscreen_linux.cpp new file mode 100644 index 0000000..3797ce6 --- /dev/null +++ b/linux/logoscreen_linux.cpp @@ -0,0 +1,7 @@ +#include "prism/logoscreen.h" + +namespace prism { + + char gLogoScreenFileName[100] = "LOGO_WIN"; + +} \ No newline at end of file diff --git a/linux/math_linux.cpp b/linux/math_linux.cpp new file mode 100644 index 0000000..8cbd183 --- /dev/null +++ b/linux/math_linux.cpp @@ -0,0 +1,11 @@ +#include "prism/math.h" + +#include + +namespace prism { + + double fstsqrt(double x) { + return sqrt(x); + } + +} \ No newline at end of file diff --git a/linux/memoryhandler_linux.cpp b/linux/memoryhandler_linux.cpp new file mode 100644 index 0000000..5848820 --- /dev/null +++ b/linux/memoryhandler_linux.cpp @@ -0,0 +1,15 @@ +#include "prism/memoryhandler.h" + +#include "prism/math.h" + +namespace prism { + + int getAvailableTextureMemory() { + return INF; + } + + int getAvailableSoundMemory() { + return INF; + } + +} \ No newline at end of file diff --git a/linux/netplay_linux.cpp b/linux/netplay_linux.cpp new file mode 100644 index 0000000..79c9400 --- /dev/null +++ b/linux/netplay_linux.cpp @@ -0,0 +1,672 @@ +#include "prism/netplay.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace prism { + +#define NETPLAY_VERSION 2 + + static struct { + FileHandler mLogFile; + char mText[2048]; + } gNetplayLogData; + + static void netplayHardwareLogToFile(FileHandler& tFileHandler, const char* tText) { + + if (tFileHandler == FILEHND_INVALID) { + createDirectory("$pc/debug"); + tFileHandler = fileOpen("$pc/debug/netplay_log.txt", O_WRONLY); + } + if (tFileHandler == FILEHND_INVALID) return; + + fileWrite(tFileHandler, tText, strlen(tText)); + } + + static void flushNetplayLog() { + if (gNetplayLogData.mLogFile == FILEHND_INVALID) return; + + fileFlush(gNetplayLogData.mLogFile); + } + + static void netplayLogprintf(const char* tFormatString, ...) { + char* logEntry = gNetplayLogData.mText; + char* writePoint = gNetplayLogData.mText + strlen(logEntry); + va_list args; + va_start(args, tFormatString); + vsprintf(writePoint, tFormatString, args); + va_end(args); + } + + static void netplayLogCommit() { + netplayHardwareLogToFile(gNetplayLogData.mLogFile, gNetplayLogData.mText); + gNetplayLogData.mText[0] = '\0'; + } + + static void netplayLogFormatFunc(const char* tFormatString, ...) { + char text[2048]; + va_list args; + va_start(args, tFormatString); + vsprintf(text, tFormatString, args); + va_end(args); + + netplayLogprintf("%s\n", text); + } + +#define netplayLogBegin() {netplayLogprintf("[%s::%s, line %d] ", __FILE__, __FUNCTION__, __LINE__);} +#define netplayLogFormat(x, ...) {netplayLogBegin(); netplayLogFormatFunc(x, __VA_ARGS__); netplayLogCommit();} +#define netplayLog(x) {logBegin(); netplayLogprintf(x); netplayLogprintf("\n"); netplayLogCommit( );} + + struct NetplaySingleFrameInput { + uint8_t mKeys[SDL_NUM_SCANCODES]; + uint8_t mButtons[CONTROLLER_BUTTON_AMOUNT_PRISM]; + }; + +#define NETPLAY_SENT_FRAME_COUNT_PER_PACKAGE 5 + struct StandardNetplayPackage { + uint32_t mMagic; + uint32_t mVersion; + uint32_t mNegotiationIndex; + uint64_t mUnixTimestamp; + int64_t mSyncedFrameIndex; + uint64_t mInputDelay; + int64_t mConnectSyncFrame; + + NetplaySingleFrameInput mFrameInput[NETPLAY_SENT_FRAME_COUNT_PER_PACKAGE]; + }; + + typedef struct { + int mNecessaryFrameDelay; + + bool mHasEstablishedInputDelay; + int mReceivedFrameDelayEstablishmentFrames; + + } FrameDelayInfo; + + static struct + { + ENetHost* mServer; + bool mIsHost; + std::vector mPeers; + + void(*mConnectCB)(void*); + void* mConnectCBCaller; + + Buffer(*mSyncGatherCB)(void*); + void* mSyncGatherCBCaller; + int(*mSyncCheckCB)(void*, const Buffer&, const Buffer&); + void* mSyncCheckCBCaller; + + void(*mDesyncCB)(void*); + void* mDesyncCBCaller; + void(*mDisconnectCB)(void*, const std::string&); + void* mDisconnectCBCaller; + + std::deque mPreviousFrameSyncData; + + uint64_t mLastReceivedTimeStamp; + int mSyncedFrameIndex; + int64_t mLastReceivedFrameIndex; + int mInputDelay; + int mConnectSyncFrame; + uint32_t mNegotiationIndex; + + FrameDelayInfo mFrameDelayInfo; + + bool mIsEnetInitialized; + } gNetplayData; + + void initNetplay() + { + if (!gNetplayData.mIsEnetInitialized) + { + if (enet_initialize() != 0) + { + logError("[Netplay] Unabled to load enet"); + } + atexit(enet_deinitialize); + gNetplayData.mIsEnetInitialized = true; + } + + gNetplayData.mConnectCB = NULL; + gNetplayData.mIsHost = false; + gNetplayData.mSyncedFrameIndex = 0; + } + + static void destroyClient(); + + static void resetNetplay() { + if (gNetplayData.mServer) { + destroyClient(); + } + gNetplayData.mIsHost = false; + gNetplayData.mPeers.clear(); + + gNetplayData.mConnectCB = NULL; + gNetplayData.mSyncGatherCB = NULL; + gNetplayData.mSyncCheckCB = NULL; + gNetplayData.mDesyncCB = NULL; + gNetplayData.mDisconnectCB = NULL; + + gNetplayData.mPreviousFrameSyncData.clear(); + + gNetplayData.mSyncedFrameIndex = 0; + gNetplayData.mInputDelay = 0; + gNetplayData.mConnectSyncFrame = 0; + gNetplayData.mNegotiationIndex = 0; + + gNetplayData.mFrameDelayInfo.mNecessaryFrameDelay = 0; + gNetplayData.mFrameDelayInfo.mHasEstablishedInputDelay = 0; + gNetplayData.mFrameDelayInfo.mReceivedFrameDelayEstablishmentFrames = 0; + } + + void shutdownNetplay() + { + resetNetplay(); + } + + static void createHostServer() + { + ENetAddress address; + /* Bind the server to the default localhost. */ + /* A specific host address can be specified by */ + /* enet_address_set_host (& address, "x.x.x.x"); */ + address.host = ENET_HOST_ANY; + /* Bind the server to port 1234. */ + address.port = 1234; + gNetplayData.mServer = enet_host_create(&address /* the address to bind the server host to */, + 32 /* allow up to 32 clients and/or outgoing connections */, + 2 /* allow up to 2 channels to be used, 0 and 1 */, + 0 /* assume any amount of incoming bandwidth */, + 0 /* assume any amount of outgoing bandwidth */); + if (gNetplayData.mServer == NULL) + { + logError("[Netplay] An error occurred while trying to create an ENet server host."); + } + + gNetplayData.mFrameDelayInfo.mHasEstablishedInputDelay = false; + gNetplayData.mFrameDelayInfo.mReceivedFrameDelayEstablishmentFrames = 0; + gNetplayData.mFrameDelayInfo.mNecessaryFrameDelay = 0; + gNetplayData.mSyncedFrameIndex = 0; + gNetplayData.mConnectSyncFrame = 0; + gNetplayData.mNegotiationIndex = 0; + gNetplayData.mInputDelay = 0; + gNetplayData.mLastReceivedTimeStamp = 0; + gNetplayData.mIsHost = true; + setInputUsedKeyboardByPlayer(1, 1); + setInputUsedKeyboardMappingByPlayer(1, 0); + setInputBufferSize(NETPLAY_SENT_FRAME_COUNT_PER_PACKAGE + 2); + gNetplayData.mPeers.clear(); + } + + void startNetplayHosting() + { + createHostServer(); + } + + static void destroyHost() + { + enet_host_destroy(gNetplayData.mServer); + gNetplayData.mServer = NULL; + } + + void stopNetplayHosting() + { + destroyHost(); + } + + bool isNetplayHost() { + return gNetplayData.mServer && gNetplayData.mIsHost; + } + + bool isNetplayActive() + { + return gNetplayData.mServer; + } + + bool isNetplayConnecting() { + return isNetplayActive() && !gNetplayData.mPeers.empty() && !gNetplayData.mConnectSyncFrame; + } + + bool isNetplaySyncing() + { + return isNetplayActive() && !gNetplayData.mPeers.empty() && (!gNetplayData.mConnectSyncFrame || (gNetplayData.mConnectSyncFrame && gNetplayData.mSyncedFrameIndex < gNetplayData.mConnectSyncFrame)); + } + + void shutdownScreenNetplay() + { + gNetplayData.mPreviousFrameSyncData.clear(); + setNetplaySyncCBs(nullptr, nullptr, nullptr, nullptr); + setNetplayDesyncCB(nullptr, nullptr); + } + + static void createClient() + { + gNetplayData.mServer = enet_host_create(NULL /* client host */, + 1 /* allow up to 32 clients and/or outgoing connections */, + 2 /* allow up to 2 channels to be used, 0 and 1 */, + 0 /* assume any amount of incoming bandwidth */, + 0 /* assume any amount of outgoing bandwidth */); + if (gNetplayData.mServer == NULL) + { + logError("[Netplay] An error occurred while trying to create an ENet client host."); + } + + gNetplayData.mPeers.clear(); + } + + static void destroyClient() + { + enet_host_destroy(gNetplayData.mServer); + gNetplayData.mServer = NULL; + } + + static bool connectToHost(const std::string& tIP, int tPort) + { + ENetAddress address; + ENetEvent event; + ENetPeer* peer; + enet_address_set_host(&address, tIP.c_str()); + address.port = (enet_uint16)tPort; + /* Initiate the connection, allocating the two channels 0 and 1. */ + peer = enet_host_connect(gNetplayData.mServer, &address, 2, 0); + if (peer == NULL) + { + logError("[Netplay] No available peers for initiating an ENet connection."); + return false; + } + /* Wait up to 5 seconds for the connection attempt to succeed. */ + if (enet_host_service(gNetplayData.mServer, &event, 5000) > 0 && + event.type == ENET_EVENT_TYPE_CONNECT) + { + logFormat("Connection to host succeeded."); + gNetplayData.mPeers.push_back(peer); + return true; + } + else + { + /* Either the 5 seconds are up or a disconnect event was */ + /* received. Reset the peer in the event the 5 seconds */ + /* had run out without any significant event. */ + enet_peer_reset(peer); + logFormat("[Netplay] Connection to host failed."); + return false; + } + } + + bool joinNetplayHost(const std::string& tIP, int tPort) + { + createClient(); + if (!connectToHost(tIP, tPort)) + { + destroyClient(); + return false; + } + + gNetplayData.mFrameDelayInfo.mHasEstablishedInputDelay = false; + gNetplayData.mFrameDelayInfo.mReceivedFrameDelayEstablishmentFrames = 0; + gNetplayData.mFrameDelayInfo.mNecessaryFrameDelay = 0; + gNetplayData.mSyncedFrameIndex = -1; + gNetplayData.mConnectSyncFrame = 0; + gNetplayData.mNegotiationIndex = 0; + gNetplayData.mInputDelay = 0; + gNetplayData.mLastReceivedTimeStamp = 0; + gNetplayData.mIsHost = false; + setInputUsedKeyboardByPlayer(1, 1); + setInputUsedKeyboardMappingByPlayer(1, 0); + setInputBufferSize(NETPLAY_SENT_FRAME_COUNT_PER_PACKAGE + 2); + return true; + } + + extern void setInputForFrameDelta(int i, int tNegativeFrameDelta, const std::vector>& tKeyStates, const std::vector>& tButtonStates); + + static void receivePeerInput(const StandardNetplayPackage* package) { + std::vector> keyStates = std::vector>(NETPLAY_SENT_FRAME_COUNT_PER_PACKAGE, std::vector(SDL_NUM_SCANCODES)); + std::vector> buttonStates = std::vector>(NETPLAY_SENT_FRAME_COUNT_PER_PACKAGE, std::vector(CONTROLLER_BUTTON_AMOUNT_PRISM)); + for (size_t i = 0; i < NETPLAY_SENT_FRAME_COUNT_PER_PACKAGE; i++) + { + memcpy(keyStates[i].data(), package->mFrameInput[i].mKeys, SDL_NUM_SCANCODES); + memcpy(buttonStates[i].data(), package->mFrameInput[i].mButtons, CONTROLLER_BUTTON_AMOUNT_PRISM); + } + + const int frameDelta = int(gNetplayData.mSyncedFrameIndex) - int(package->mSyncedFrameIndex); + netplayLogFormat("[Netplay] Set input on frame %d for frames starting between %d and %d", gNetplayData.mSyncedFrameIndex, gNetplayData.mSyncedFrameIndex - frameDelta - NETPLAY_SENT_FRAME_COUNT_PER_PACKAGE + 1, gNetplayData.mSyncedFrameIndex - frameDelta); + setInputForFrameDelta(1, -frameDelta, keyStates, buttonStates); + } + + static void establishFrameDelayWithPeer(const StandardNetplayPackage* package) + { + static constexpr auto INPUT_DELAY_SAFETY_BUFFER = 10; + + if (gNetplayData.mIsHost) { + if (!gNetplayData.mInputDelay && package->mUnixTimestamp) + { + const auto nowMs = int64_t(getUnixTimestampMilliseconds()); + const auto timeDelayMs = (nowMs - package->mUnixTimestamp) / 2; + + const auto frameTimeMs = (1.0 / double(getFramerate())) * 1000; + auto frameDelay = int((timeDelayMs / frameTimeMs)) + 1; + + if (frameDelay <= 20) + { + netplayLogFormat("[Netplay] adding frame delay for consideration %d", frameDelay); + gNetplayData.mFrameDelayInfo.mNecessaryFrameDelay = max(gNetplayData.mFrameDelayInfo.mNecessaryFrameDelay, frameDelay); + gNetplayData.mFrameDelayInfo.mReceivedFrameDelayEstablishmentFrames++; + } + else + { + netplayLogFormat("[Netplay] discarding potential frame delay %d, substitute with max", frameDelay); + gNetplayData.mFrameDelayInfo.mNecessaryFrameDelay = max(gNetplayData.mFrameDelayInfo.mNecessaryFrameDelay, 20); + gNetplayData.mFrameDelayInfo.mReceivedFrameDelayEstablishmentFrames++; + } + + if (gNetplayData.mFrameDelayInfo.mReceivedFrameDelayEstablishmentFrames >= 10) + { + gNetplayData.mInputDelay = gNetplayData.mFrameDelayInfo.mNecessaryFrameDelay + INPUT_DELAY_SAFETY_BUFFER; + setInputDelay(gNetplayData.mInputDelay); + netplayLogFormat("[Netplay] Peer input delay set to %d", gNetplayData.mInputDelay); + gNetplayData.mFrameDelayInfo.mHasEstablishedInputDelay = true; + gNetplayData.mConnectSyncFrame = gNetplayData.mSyncedFrameIndex + gNetplayData.mInputDelay * 2; + netplayLogFormat("[Netplay] Connect sync frame set to %d", gNetplayData.mConnectSyncFrame); + } + } + } + else { + if (!gNetplayData.mInputDelay) + { + if (!package->mInputDelay) + { + gNetplayData.mLastReceivedTimeStamp = package->mUnixTimestamp; + netplayLogFormat("[Netplay] Received ping package with timestamp %llu", package->mUnixTimestamp); + } + else + { + gNetplayData.mInputDelay = int(package->mInputDelay); + setInputDelay(gNetplayData.mInputDelay); + netplayLogFormat("[Netplay] Input delay set to %d", gNetplayData.mInputDelay); + gNetplayData.mFrameDelayInfo.mHasEstablishedInputDelay = true; + gNetplayData.mFrameDelayInfo.mNecessaryFrameDelay = gNetplayData.mInputDelay - INPUT_DELAY_SAFETY_BUFFER; + gNetplayData.mSyncedFrameIndex = int(package->mSyncedFrameIndex) + gNetplayData.mFrameDelayInfo.mNecessaryFrameDelay; + netplayLogFormat("[Netplay] Synched frame established at %d", gNetplayData.mSyncedFrameIndex); + gNetplayData.mConnectSyncFrame = int(package->mConnectSyncFrame); + netplayLogFormat("[Netplay] Connect sync frame set to %d", gNetplayData.mConnectSyncFrame); + } + } + } + } + + static void handlePeerSyncCheck(StandardNetplayPackage* package, const Buffer& b) { + if (!b.mLength || !gNetplayData.mSyncCheckCB) return; + + const int frameDelta = int(gNetplayData.mSyncedFrameIndex) - int(package->mSyncedFrameIndex); + if (frameDelta < 0 || frameDelta >= gNetplayData.mPreviousFrameSyncData.size()) return; + const auto& correspondingFrameData = *(gNetplayData.mPreviousFrameSyncData.rbegin() + frameDelta); + const auto isSame = gNetplayData.mSyncCheckCB(gNetplayData.mSyncCheckCB, correspondingFrameData, b); + if (!isSame) + { + logWarningFormat("[Netplay] desync detected on frame %d, last received package %d, frame delta %d!", gNetplayData.mSyncedFrameIndex, package->mSyncedFrameIndex, frameDelta); + netplayLog("[Netplay] desync detected"); + if (gNetplayData.mDesyncCB) { + gNetplayData.mDesyncCB(gNetplayData.mDesyncCBCaller); + } + } + } + + static void updateNetplayEvents() + { + ENetEvent event; + while (gNetplayData.mServer && enet_host_service(gNetplayData.mServer, &event, 0) > 0) + { + StandardNetplayPackage* package = nullptr; + switch (event.type) + { + case ENET_EVENT_TYPE_CONNECT: + logFormat("A new client connected from %x:%u.\n", + event.peer->address.host, + event.peer->address.port); + /* Store any relevant client information here. */ + event.peer->data = "Client information"; + gNetplayData.mPeers.push_back(event.peer); + if (isNetplayHost()) { + gNetplayData.mSyncedFrameIndex = 0; + } + logFormat("Connection received from peer"); + break; + case ENET_EVENT_TYPE_RECEIVE: + assert(event.packet->dataLength >= sizeof(StandardNetplayPackage)); + package = (StandardNetplayPackage*)event.packet->data; + + if (package->mVersion != NETPLAY_VERSION) { + gNetplayData.mDisconnectCB(gNetplayData.mDisconnectCBCaller, "version"); + break; + } + + gNetplayData.mLastReceivedFrameIndex = package->mSyncedFrameIndex; + if (package->mNegotiationIndex == gNetplayData.mNegotiationIndex) { + if (!gNetplayData.mConnectSyncFrame) + { + establishFrameDelayWithPeer(package); + } + + if (gNetplayData.mFrameDelayInfo.mHasEstablishedInputDelay) + { + receivePeerInput(package); + handlePeerSyncCheck(package, makeBuffer(event.packet->data + sizeof(StandardNetplayPackage) + sizeof(int32_t), *((int32_t*)(event.packet->data + sizeof(StandardNetplayPackage))))); + } + } + + /* Clean up the packet now that we're done using it. */ + enet_packet_destroy(event.packet); + + break; + + case ENET_EVENT_TYPE_DISCONNECT: + logFormat("%s disconnected.\n", event.peer->data); + /* Reset the peer's client information. */ + event.peer->data = NULL; + if (gNetplayData.mDisconnectCB) { + gNetplayData.mDisconnectCB(gNetplayData.mDisconnectCBCaller, ""); + } + } + } + } + + extern void gatherWindowsInputStateForPastFrames(int i, size_t pastFrames, std::vector>& tKeyStates, std::vector>& tButtonStates); + + static void updateNetplaySendingFrame() { + StandardNetplayPackage netplayPackage; + netplayPackage.mVersion = NETPLAY_VERSION; + netplayPackage.mMagic = 503; + netplayPackage.mUnixTimestamp = gNetplayData.mIsHost ? getUnixTimestampMilliseconds() : gNetplayData.mLastReceivedTimeStamp; + netplayPackage.mSyncedFrameIndex = gNetplayData.mSyncedFrameIndex; + netplayPackage.mConnectSyncFrame = gNetplayData.mConnectSyncFrame; + netplayPackage.mNegotiationIndex = gNetplayData.mNegotiationIndex; + netplayPackage.mInputDelay = gNetplayData.mInputDelay; + + std::vector> keyStates; + std::vector> buttonStates; + gatherWindowsInputStateForPastFrames(0, NETPLAY_SENT_FRAME_COUNT_PER_PACKAGE, keyStates, buttonStates); + + assert(keyStates.size() == NETPLAY_SENT_FRAME_COUNT_PER_PACKAGE); + assert(buttonStates.size() == NETPLAY_SENT_FRAME_COUNT_PER_PACKAGE); + + for (size_t i = 0; i < keyStates.size(); i++) + { + memcpy(netplayPackage.mFrameInput[i].mKeys, keyStates[i].data(), keyStates[i].size()); + memcpy(netplayPackage.mFrameInput[i].mButtons, buttonStates[i].data(), buttonStates[i].size()); + } + Buffer packageBuffer = makeBufferEmptyOwned(); + appendBufferBuffer(&packageBuffer, makeBuffer(&netplayPackage, sizeof(StandardNetplayPackage))); + + if (gNetplayData.mSyncGatherCB) + { + Buffer newSyncData = gNetplayData.mSyncGatherCB(gNetplayData.mSyncGatherCBCaller); + appendBufferInt32(&packageBuffer, newSyncData.mLength); + appendBufferBuffer(&packageBuffer, newSyncData); + gNetplayData.mPreviousFrameSyncData.push_back(newSyncData); + if (gNetplayData.mPreviousFrameSyncData.size() > NETPLAY_SENT_FRAME_COUNT_PER_PACKAGE) + { + auto& buffer = gNetplayData.mPreviousFrameSyncData.front(); + freeBuffer(buffer); + gNetplayData.mPreviousFrameSyncData.pop_front(); + } + } + else + { + appendBufferInt32(&packageBuffer, 0); + } + + sendNetplayData(packageBuffer); + freeBuffer(packageBuffer); + } + + static void updateNetplayStart() + { + if (gNetplayData.mConnectSyncFrame && gNetplayData.mSyncedFrameIndex == gNetplayData.mConnectSyncFrame) + { + setRandomSeed(gNetplayData.mConnectSyncFrame); + if (gNetplayData.mConnectCB) { + gNetplayData.mConnectCB(gNetplayData.mConnectCBCaller); + } + } + } + + extern void gatherWindowsInputStateForLogging(std::vector& tConfirmationStates, std::vector>& tKeyStates, std::vector>& tButtonStates); + + static void updateLoggedInput() { + if (!gNetplayData.mConnectSyncFrame || (gNetplayData.mSyncedFrameIndex < gNetplayData.mConnectSyncFrame)) return; + + std::vector confirmationStates; + std::vector> keyStates; + std::vector> buttonStates; + gatherWindowsInputStateForLogging(confirmationStates, keyStates, buttonStates); + + for (int i = 0; i < 2; i++) { + netplayLogFormat("[Netplay] Frame %d start input logging for player %d ", gNetplayData.mSyncedFrameIndex, i + 1); + netplayLogFormat("[Netplay] Frame %d Player %d confirmation state: %d", gNetplayData.mSyncedFrameIndex, i + 1, confirmationStates[i]); + + std::string keyStateString = ""; + for (int j = 0; j < keyStates[i].size(); j++) + { + keyStateString += std::to_string(keyStates[i][j]); + keyStateString += " "; + } + netplayLogFormat("[Netplay] Frame %d Player %d key states: %s", gNetplayData.mSyncedFrameIndex, i + 1, keyStateString.c_str()); + + std::string buttonInputString = ""; + for (int j = 0; j < buttonStates[i].size(); j++) + { + buttonInputString += std::to_string(buttonStates[i][j]); + buttonInputString += " "; + } + netplayLogFormat("[Netplay] Frame %d Player %d button states: %s", gNetplayData.mSyncedFrameIndex, i + 1, buttonInputString.c_str()); + } + + flushNetplayLog(); + } + + static int checkConfirmedInputAndWaitIfNecessary() { + if (!gNetplayData.mConnectSyncFrame || (gNetplayData.mSyncedFrameIndex < gNetplayData.mConnectSyncFrame)) return 1; + + const auto ret = isNetplayInputConfirmed(); + if (!ret) + { + netplayLogFormat("[Netplay] Using unconfirmed input for frame %d.", gNetplayData.mSyncedFrameIndex - getInputDelay()); + } + return ret; + } + + void updateNetplay() { + if (!gNetplayData.mServer) return; + updateNetplaySendingFrame(); + updateNetplayEvents(); + if (!gNetplayData.mServer) return; + + updateLoggedInput(); + checkConfirmedInputAndWaitIfNecessary(); + updateNetplayStart(); + if (gNetplayData.mSyncedFrameIndex >= 0) + { + gNetplayData.mSyncedFrameIndex++; + } + } + + void sendNetplayData(const Buffer& tData) { + ENetPacket* packet = enet_packet_create(tData.mData, + tData.mLength, + ENET_PACKET_FLAG_RELIABLE); + + for (auto peer : gNetplayData.mPeers) + { + enet_peer_send(peer, 0, packet); + } + + enet_host_flush(gNetplayData.mServer); + } + + void setNetplayConnectCB(void(*tCB)(void*), void* tCaller) + { + gNetplayData.mConnectCB = tCB; + gNetplayData.mConnectCBCaller = tCaller; + } + + void setNetplaySyncCBs(Buffer(*tGatherCB)(void*), void* tGatherCaller, int(*tCheckCB)(void*, const Buffer&, const Buffer&), void* tCheckCaller) + { + gNetplayData.mSyncGatherCB = tGatherCB; + gNetplayData.mSyncGatherCBCaller = tGatherCaller; + gNetplayData.mSyncCheckCB = tCheckCB; + gNetplayData.mSyncCheckCBCaller = tCheckCaller; + } + + void setNetplayDesyncCB(void(*tCB)(void*), void* tCaller) { + gNetplayData.mDesyncCB = tCB; + gNetplayData.mDesyncCBCaller = tCaller; + } + + void setNetplayDisconnectCB(void(*tCB)(void*, const std::string&), void* tCaller) { + gNetplayData.mDisconnectCB = tCB; + gNetplayData.mDisconnectCBCaller = tCaller; + } + + void renegotiateNetplayConnection() { + updateNetplayEvents(); // flush packages so far + + gNetplayData.mConnectCB = NULL; + gNetplayData.mConnectCBCaller = NULL; + + gNetplayData.mFrameDelayInfo.mHasEstablishedInputDelay = false; + if (!isNetplayHost()) + { + gNetplayData.mSyncedFrameIndex = -1; + } + else + { + gNetplayData.mSyncedFrameIndex = 0; + } + gNetplayData.mFrameDelayInfo.mNecessaryFrameDelay = 0; + gNetplayData.mFrameDelayInfo.mReceivedFrameDelayEstablishmentFrames = 0; + gNetplayData.mLastReceivedTimeStamp = 0; + gNetplayData.mConnectSyncFrame = 0; + gNetplayData.mNegotiationIndex++; + gNetplayData.mInputDelay = 0; + } + + int getNetplaySyncFrame() { + return gNetplayData.mSyncedFrameIndex; + } + int getNetplayLastReceivedFrame() + { + return int(gNetplayData.mLastReceivedFrameIndex); + } + +} \ No newline at end of file diff --git a/linux/romdisk_linux.cpp b/linux/romdisk_linux.cpp new file mode 100644 index 0000000..02996d1 --- /dev/null +++ b/linux/romdisk_linux.cpp @@ -0,0 +1,521 @@ +#include "prism/windows/romdisk_win.h" + +#include +#include + +#include "prism/datastructures.h" +#include "prism/system.h" +#include "prism/log.h" +#include "prism/memoryhandler.h" + +#ifdef _WIN32 +#pragma warning(push) +#pragma warning(disable : 4267) +#pragma warning(disable : 4312) +#pragma warning(disable : 4311) +#pragma warning(disable : 4302) +#endif + +#ifdef __EMSCRIPTEN__ +#define SDL_strncasecmp SDL_strncasecmp +#elif defined (VITA) +static int caseIndependentCompareForRomdisk(const char* str1, const char* str2, size_t len) { + for (size_t i = 0; i < len; i++) { + char c1 = str1[i]; + char c2 = str2[i]; + + if (c1 >= 'A' && c1 <= 'Z') c1 += 'a' - 'A'; + if (c2 >= 'A' && c2 <= 'Z') c2 += 'a' - 'A'; + + if (c1 != c2) { + return c1 - c2; + } + + if (c1 == '\0' || c2 == '\0') { + return (c2 == '\0') ? -1 : 1; + } + } + return 0; +} +#define SDL_strncasecmp caseIndependentCompareForRomdisk +#endif + +namespace prism { + + static StringMap gRomdiskHandlers; + +#define O_MODE_MASK 0x0f /**< \brief Mask for mode numbers */ + //#define O_TRUNC 0x0100 /* Truncate */ +#define O_ASYNC 0x0200 /**< \brief Open for asynchronous I/O */ +//#define O_NONBLOCK 0x0400 /* Open for non-blocking I/O */ +#define O_DIR 0x1000 /**< \brief Open as directory */ +#define O_META 0x2000 /**< \brief Open as metadata */ + +#define MAX_RD_FILES 1024 + + typedef struct { + char magic[8]; /* Should be "-rom1fs-" */ + uint32_t full_size; /* Full size of the file system */ + uint32_t checksum; /* Checksum */ + char volume_name[16]; /* Volume name (zero-terminated) */ + } romdisk_hdr_t; + + /* File header info; note that this header plus filename must be a multiple of + 16 bytes, and the following file data must also be a multiple of 16 bytes. */ + typedef struct { + uint32_t next_header; /* Offset of next header */ + uint32_t spec_info; /* Spec info */ + uint32_t size; /* Data size */ + uint32_t checksum; /* File checksum */ + char filename[16]; /* File name (zero-terminated) */ + } romdisk_file_t; + + + /* Util function to reverse the byte order of a uint32 */ + static uint32_t ntohl_32(const void* data) { + const uint8_t* d = (const uint8_t*)data; + return (d[0] << 24) | (d[1] << 16) | (d[2] << 8) | (d[3] << 0); + } + + /********************************************************************************/ + + /* A single mounted romdisk image; a pointer to one of these will be in our + VFS struct for each mount. */ + typedef struct rd_image { + + int own_buffer; /* Do we own the memory? */ + const uint8_t* image; /* The actual image */ + const romdisk_hdr_t* hdr; /* Pointer to the header */ + uint32_t files; /* Offset in the image to the files area */ + char mountpath[1024]; + } rd_image_t; + + static StringMap gRomdiskMapping; + + /********************************************************************************/ + /* File primitives */ + + /* File handles.. I could probably do this with a linked list, but I'm just + too lazy right now. =) */ + static struct { + int32_t index; /* romfs image index */ + int dir; /* >0 if a directory */ + uint32_t ptr; /* Current read position in bytes */ + uint32_t size; /* Length of file in bytes */ + // dirent_t dirent; /* A static dirent to pass back to clients */ + rd_image_t* mnt; /* Which mount instance are we using? */ + } gFileHandlers[MAX_RD_FILES]; + + /* Given a filename and a starting romdisk directory listing (byte offset), + search for the entry in the directory and return the byte offset to its + entry. */ + static uint32_t romdisk_find_object(rd_image_t* mnt, const char* fn, size_t fnlen, int dir, uint32_t offset) { + uint32_t i, ni, type; + const romdisk_file_t* fhdr; + + i = offset; + + do { + /* Locate the entry, next pointer, and type info */ + fhdr = (const romdisk_file_t*)(mnt->image + i); + ni = ntohl_32(&fhdr->next_header); + type = ni & 0x0f; + ni = ni & 0xfffffff0; + + /* Check the type */ + if (!dir) { + if ((type & 3) != 2) { + i = ni; + + if (!i) + break; + else + continue; + } + } + else { + if ((type & 3) != 1) { + i = ni; + + if (!i) + break; + else + continue; + } + } + + /* Check filename */ + if ((strlen(fhdr->filename) == fnlen) && (!SDL_strncasecmp(fhdr->filename, fn, fnlen))) { + /* Match: return this index */ + return i; + } + + i = ni; + } while (i != 0); + + /* Didn't find it */ + return 0; + } + + /* Locate an object anywhere in the image, starting at the root, and + expecting a fully qualified path name. This is analogous to the + find_object_path in iso9660. + fn: object filename (absolute path) + dir: 0 if looking for a file, 1 if looking for a dir + It will return an offset in the romdisk image for the object. */ + static uint32_t romdisk_find(rd_image_t* mnt, const char* fn, int dir) { + const char* cur; + uint32_t i; + const romdisk_file_t* fhdr; + + /* If the object is in a sub-tree, traverse the trees looking + for the right directory. */ + i = mnt->files; + + cur = strchr(fn, '/'); + while (cur) { + if (cur != fn) { + i = romdisk_find_object(mnt, fn, cur - fn, 1, i); + + if (i == 0) return 0; + + fhdr = (const romdisk_file_t*)(mnt->image + i); + i = ntohl_32(&fhdr->spec_info); + } + + fn = cur + 1; + cur = strchr(fn, '/'); + } + + /* Locate the file in the resulting directory */ + if (*fn) { + i = romdisk_find_object(mnt, fn, strlen(fn), dir, i); + return i; + } + else { + if (!dir) + return 0; + else + return i; + } + } + + static void getPotentialMountFromPath(char* tPotentialMount, const char* tPath) { + tPotentialMount[0] = '\0'; + if (tPath[0] == '$') tPath++; + if (tPath[0] != '/') return; + + strcpy(tPotentialMount, tPath + 1); + char* endPos = strchr(tPotentialMount, '/'); + if (endPos != NULL) *endPos = '\0'; + } + + + + static rd_image_t* getRomdiskImageFromPath(char* tPath) { + char potentialMount[1024]; + getPotentialMountFromPath(potentialMount, tPath); + return (rd_image_t*)string_map_get(&gRomdiskMapping, potentialMount); + } + + /* Open a file or directory */ + + FileHandler fileOpenRomdisk(char* tPath, int tFlags) { + int fd; + uint32_t filehdr; + const romdisk_file_t* fhdr; + rd_image_t* mnt = getRomdiskImageFromPath(tPath); + + /* Make sure they don't want to open things as writeable */ + if (tFlags != O_RDONLY) { + errno = EPERM; + return NULL; + } + + const char* romdiskPath = strchr(tPath + 1, '/'); + if (romdiskPath == NULL) romdiskPath = tPath; + + /* No blank filenames */ + if (romdiskPath[0] == 0) + romdiskPath = ""; + + /* Look for the file */ + filehdr = romdisk_find(mnt, romdiskPath + 1, tFlags & O_DIR); + + if (filehdr == 0) { + errno = ENOENT; + return NULL; + } + + /* Find a free file handle */ + // mutex_lock(&fh_mutex); + + for (fd = 0; fd < MAX_RD_FILES; fd++) + if (gFileHandlers[fd].index == 0) { + gFileHandlers[fd].index = -1; + break; + } + + // mutex_unlock(&fh_mutex); + + if (fd >= MAX_RD_FILES) { + errno = ENFILE; + return NULL; + } + + /* Fill the fd structure */ + fhdr = (const romdisk_file_t*)(mnt->image + filehdr); + gFileHandlers[fd].index = filehdr + sizeof(romdisk_file_t) + (strlen(fhdr->filename) / 16) * 16; + gFileHandlers[fd].dir = (tFlags & O_DIR) ? 1 : 0; + gFileHandlers[fd].ptr = 0; + gFileHandlers[fd].size = ntohl_32(&fhdr->size); + gFileHandlers[fd].mnt = mnt; + + char handlerString[100]; + sprintf(handlerString, "%d", fd); + string_map_push(&gRomdiskHandlers, handlerString, NULL); + + return (FileHandler)(long int)fd; + } + + /* Close a file or directory */ + int fileCloseRomdisk(FileHandler tHandler) { + int fd = (long int)tHandler; + + /* Check that the fd is valid */ + if (fd < MAX_RD_FILES) { + /* No need to lock the mutex: this is an atomic op */ + gFileHandlers[fd].index = 0; + } + + char handlerString[100]; + sprintf(handlerString, "%d", fd); + string_map_remove(&gRomdiskHandlers, handlerString); + + return 0; + } + + /* Read from a file */ + size_t fileReadRomdisk(FileHandler tHandler, void* tBuffer, size_t tCount) { + int fd = (long int)tHandler; + + /* Check that the fd is valid */ + if (fd >= MAX_RD_FILES || gFileHandlers[fd].index == 0 || gFileHandlers[fd].dir) { + logError("Invalid file handler"); + logErrorInteger(fd); + recoverFromError(); + } + + /* Is there enough left? */ + if ((gFileHandlers[fd].ptr + tCount) > gFileHandlers[fd].size) + tCount = gFileHandlers[fd].size - gFileHandlers[fd].ptr; + + /* Copy out the requested amount */ + memcpy(tBuffer, gFileHandlers[fd].mnt->image + gFileHandlers[fd].index + gFileHandlers[fd].ptr, tCount); + gFileHandlers[fd].ptr += tCount; + + return tCount; + } + + /* Seek elsewhere in a file */ + size_t fileSeekRomdisk(FileHandler tHandler, size_t tOffset, int tWhence) { + int fd = (long int)tHandler; + + /* Check that the fd is valid */ + if (fd >= MAX_RD_FILES || gFileHandlers[fd].index == 0 || gFileHandlers[fd].dir) { + logError("Invalid file handler"); + logErrorInteger(fd); + recoverFromError(); + } + + int32_t offset = (int32_t)tOffset; + /* Update current position according to arguments */ + switch (tWhence) { + case SEEK_SET: + if (offset < 0) { + logError("Invalid offset"); + logErrorInteger(tOffset); + recoverFromError(); + } + + gFileHandlers[fd].ptr = tOffset; + break; + + case SEEK_CUR: + if (offset < 0 && (-offset) >(int32_t)gFileHandlers[fd].ptr) { + logError("Invalid offset"); + logErrorInteger(tOffset); + recoverFromError(); + } + + gFileHandlers[fd].ptr += tOffset; + break; + + case SEEK_END: + if (offset < 0 && (-offset) >(int32_t)gFileHandlers[fd].size) { + logError("Invalid offset"); + logErrorInteger(tOffset); + recoverFromError(); + } + + gFileHandlers[fd].ptr = gFileHandlers[fd].size + tOffset; + break; + + default: + logError("Invalid whence"); + logErrorInteger(tWhence); + recoverFromError(); + } + + /* Check bounds */ + if (gFileHandlers[fd].ptr > gFileHandlers[fd].size) gFileHandlers[fd].ptr = gFileHandlers[fd].size; + + return gFileHandlers[fd].ptr; + } + + /* Tell where in the file we are */ + size_t fileTellRomdisk(FileHandler tHandler) { + int fd = (long int)tHandler; + + if (fd >= MAX_RD_FILES || gFileHandlers[fd].index == 0 || gFileHandlers[fd].dir) { + logError("Invalid file handler"); + logErrorInteger(fd); + recoverFromError(); + } + + return gFileHandlers[fd].ptr; + } + + /* Tell how big the file is */ + size_t fileTotalRomdisk(FileHandler tHandler) { + int fd = (long int)tHandler; + + if (fd >= MAX_RD_FILES || gFileHandlers[fd].index == 0 || gFileHandlers[fd].dir) { + logError("Invalid file handler"); + logErrorInteger(fd); + recoverFromError(); + } + + return gFileHandlers[fd].size; + } + + + /* Are we initialized? */ + static int gInitted = 0; + + /* Initialize the file system */ + void initRomdisks() { + if (gInitted) return; + + gRomdiskHandlers = new_string_map(); + gRomdiskMapping = new_string_map(); + + /* Reset fd's */ + memset(gFileHandlers, 0, sizeof gFileHandlers); + + /* Mark the first as active so we can have an error FD of zero */ + gFileHandlers[0].index = -1; + + gInitted = 1; + } + + void shutdownRomdisks() { + if (!gInitted) return; + gInitted = 0; + } + + void mountRomdiskWindowsFromBuffer(Buffer b, const char* tMountPath) + { + int isAlreadyMounted = string_map_contains(&gRomdiskMapping, tMountPath); + if (isAlreadyMounted) { + logError("Unable to mount. Already mounted."); + logErrorString(tMountPath); + recoverFromError(); + } + + + char* img = (char*)b.mData; + + const romdisk_hdr_t* hdr; + rd_image_t* mnt; + int own_buffer = 1; + + /* Are we initted? */ + if (!gInitted) { + logError("Trying to mount romdisk before init"); + logErrorString(tMountPath); + recoverFromError(); + } + + /* Check the image and print some info about it */ + hdr = (const romdisk_hdr_t*)img; + + if (strncmp((char*)img, "-rom1fs-", 8)) { + logError("Rom disk image is not a ROMFS image\n"); + logErrorPointer(img); + recoverFromError(); + } + + /* Create a mount struct */ + mnt = (rd_image_t*)allocMemory(sizeof(rd_image_t)); + mnt->own_buffer = own_buffer; + mnt->image = (const uint8_t*)img; + mnt->hdr = hdr; + mnt->files = sizeof(romdisk_hdr_t) + + (strlen(hdr->volume_name) / 16) * 16; + strcpy(mnt->mountpath, tMountPath); + + /* Add it to our mount list */ + // mutex_lock(&fh_mutex); + string_map_push_owned(&gRomdiskMapping, tMountPath, mnt); + // mutex_unlock(&fh_mutex); + } + + /* Mount a romdisk image; must have called fs_romdisk_init() earlier. + Also note that we do _not_ take ownership of the image data if + own_buffer is 0, so if you alloc'd that buffer, you must + also free it after the unmount. If own_buffer is non-zero, then + we free the buffer when it is unmounted. */ + void mountRomdiskWindows(const char* tFilePath, const char* tMountPath) { + Buffer b = fileToBuffer(tFilePath); + mountRomdiskWindowsFromBuffer(b, tMountPath); + } + + + + /* Unmount a romdisk image */ + void unmountRomdiskWindows(const char* tMountPath) { + rd_image_t* n; + + // mutex_lock(&fh_mutex); + + n = (rd_image_t*)string_map_get(&gRomdiskMapping, tMountPath); + + /* If we own the buffer, free it */ + if (n->own_buffer) freeMemory((void*)n->image); + + string_map_remove(&gRomdiskMapping, tMountPath); + } + + int isRomdiskPath(const char* tPath) + { + if (!gInitted) return 0; + char mount[1024]; + getPotentialMountFromPath(mount, tPath); + + return string_map_contains(&gRomdiskMapping, mount); + } + + int isRomdiskFileHandler(FileHandler tHandler) + { + if (!gInitted) return 0; + char handlerString[100]; + sprintf(handlerString, "%ld", (long int)tHandler); + return string_map_contains(&gRomdiskHandlers, handlerString); + } + +#ifdef _WIN32 +#pragma warning(pop) +#endif + +} \ No newline at end of file diff --git a/linux/saveload_linux.cpp b/linux/saveload_linux.cpp new file mode 100644 index 0000000..c59305b --- /dev/null +++ b/linux/saveload_linux.cpp @@ -0,0 +1,24 @@ +#include "prism/saveload.h" + +#include + +#include "prism/log.h" + +namespace prism { + + void savePrismGameSave(PrismSaveSlot /*tSaveSlot*/, const char* /*tFileName*/, const Buffer& /*tBuffer*/, const char* /*tApplicationName*/, const char* /*tShortDescription*/, const char* /*tLongDescription*/, const Buffer& /*tIconDataBuffer*/, const Buffer& /*tPaletteBuffer*/) {} + + Buffer loadPrismGameSave(PrismSaveSlot /*tSaveSlot*/, const char* /*tFileName*/) { + logError("Loading via prism unimplemented for Win/Emscripten"); + assert(0); + return makeBuffer(NULL, 0); + } + + void deletePrismGameSave(PrismSaveSlot /*tSaveSlot*/, const char* /*tFileName*/) {} + int isPrismSaveSlotActive(PrismSaveSlot /*tSaveSlot*/) { return 0; } + int hasPrismGameSave(PrismSaveSlot /*tSaveSlot*/, const char* /*tFileName*/) { return 0; } + size_t getAvailableSizeForSaveSlot(PrismSaveSlot /*tSaveSlot*/) { return 0; } + size_t getPrismGameSaveSize(const Buffer& /*tBuffer*/, const char* /*tApplicationName*/, const char* /*tShortDescription*/, const char* /*tLongDescription*/, const Buffer& /*tIconDataBuffer*/, const Buffer& /*tPaletteBuffer*/) { return 0; } + void setVMUDisplayIcon(void* /*tBitmap*/, bool) { } + +} \ No newline at end of file diff --git a/linux/screeneffect_linux.cpp b/linux/screeneffect_linux.cpp new file mode 100644 index 0000000..9a14a87 --- /dev/null +++ b/linux/screeneffect_linux.cpp @@ -0,0 +1,26 @@ +#include "prism/screeneffect.h" + +#include + +namespace prism { + + extern SDL_Renderer* gRenderer; + + void setScreenColor(Color tColor) { + double r, g, b; + getRGBFromColor(tColor, &r, &g, &b); + glClearColor((GLclampf)r, (GLclampf)g, (GLclampf)b, (GLclampf)1); + + disableDrawing(); + } + + void setScreenBackgroundColorRGB(double tR, double tG, double tB) + { + glClearColor((GLclampf)tR, (GLclampf)tG, (GLclampf)tB, (GLclampf)1); + } + + void unsetScreenColor() { + enableDrawing(); + } + +} \ No newline at end of file diff --git a/linux/sound_linux.cpp b/linux/sound_linux.cpp new file mode 100644 index 0000000..3322195 --- /dev/null +++ b/linux/sound_linux.cpp @@ -0,0 +1,339 @@ +#include "prism/sound.h" + +#include +#include +#include + +#include +#include +#include "prism/log.h" +#include "prism/file.h" +#include "prism/datastructures.h" +#include "prism/memoryhandler.h" +#include "prism/system.h" +#include "prism/math.h" + +#define MICROPHONE_SAMPLE_AMOUNT 128 + +#include "prism/soundeffect.h" +#include +#include "prism/windows/debugimgui_win.h" + +using namespace std; +namespace prism { + + typedef struct { + int mIsMicrophoneActive; + SDL_AudioDeviceID mMicrophone; + + int mSampleAmount; + uint8_t mSamples[MICROPHONE_SAMPLE_AMOUNT]; + int mSampleSum; + int mSamplePointer; + + int mMasterPeakVolume; + } Microphone; + + static struct { + + int mVolume; + double mPanning; + + int mHasLoadedTrack; + int mIsPlayingTrack; + int mIsPaused; + Mix_Music* mTrackChunk; + uint64_t mTimeWhenMusicPlaybackStarted; + + Microphone mMicrophone; + } gPrismWindowsSoundData; + +#ifdef _WIN32 + static void imguiPrismWindowsSoundData() { + if (ImGui::TreeNode("Sound Data")) + { + ImGui::Text("Volume: %d", gPrismWindowsSoundData.mVolume); + ImGui::Text("Panning: %f", gPrismWindowsSoundData.mPanning); + ImGui::Text("Has Loaded Track: %d", gPrismWindowsSoundData.mHasLoadedTrack); + ImGui::Text("Is Playing Track: %d", gPrismWindowsSoundData.mIsPlayingTrack); + ImGui::Text("Is Paused: %d", gPrismWindowsSoundData.mIsPaused); + ImGui::TreePop(); + } + } + + static void imguiPrismWindowsSoundMicrophone() { + if (ImGui::TreeNode("Microphone")) + { + ImGui::Text("Is Microphone Active: %d", gPrismWindowsSoundData.mMicrophone.mIsMicrophoneActive); + ImGui::Text("Sample Amount: %d", gPrismWindowsSoundData.mMicrophone.mSampleAmount); + ImGui::Text("Sample Sum: %d", gPrismWindowsSoundData.mMicrophone.mSampleSum); + ImGui::Text("Sample Pointer: %d", gPrismWindowsSoundData.mMicrophone.mSamplePointer); + ImGui::Text("Master Peak Volume: %d", gPrismWindowsSoundData.mMicrophone.mMasterPeakVolume); + ImGui::TreePop(); + } + } + + void imguiSoundHardware() { + static bool isWindowShown = false; + imguiPrismAddTab("Prism", "Sound HW", &isWindowShown); + if (isWindowShown) + { + ImGui::Begin("Drawing HW", &isWindowShown); + ImGui::Text("Volume: %f", getVolume()); + ImGui::Text("Panning: %f", getPanningValue()); + ImGui::Text("Microphone Volume: %f", getMicrophoneVolume()); + imguiPrismWindowsSoundData(); + imguiPrismWindowsSoundMicrophone(); + ImGui::End(); + } + } +#endif + + void initSound() { + gPrismWindowsSoundData.mPanning = 0; + if (!Mix_Init(MIX_INIT_OGG)) + { + logErrorFormat("Unable to init SDL Mixer: %s", SDL_GetError()); + } + if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048) == -1) + { + logErrorFormat("Unable to open audio: %s", SDL_GetError()); + } + if (Mix_AllocateChannels(1024) != 1024) + { + logErrorFormat("Unable to allocate mixer channels: %s", SDL_GetError()); + } + gPrismWindowsSoundData.mHasLoadedTrack = 0; + gPrismWindowsSoundData.mIsPlayingTrack = 0; + + setVolume(0.2); + gPrismWindowsSoundData.mMicrophone.mIsMicrophoneActive = 0; + } + + void shutdownSound() { + Mix_CloseAudio(); + } + + double getVolume() { + return gPrismWindowsSoundData.mVolume / 128.0; + } + + void setVolume(double tVolume) { + gPrismWindowsSoundData.mVolume = (int)(tVolume * 128); + Mix_VolumeMusic(gPrismWindowsSoundData.mVolume); + } + + double getPanningValue() { + return gPrismWindowsSoundData.mPanning; + } + + void setPanningValue(int tChannel, double tPanning) + { + gPrismWindowsSoundData.mPanning = tPanning; + tPanning = (tPanning + 1) * 0.5; // [-1, 1] -> [0, 1] + const uint8_t right = uint8_t(std::min(std::max(tPanning, 0.0), 1.0) * 255); + Mix_SetPanning(tChannel, 255 - right, right); + } + + static void playMusicPath(const char* tPath) { + char fullPath[1024]; + getFullPath(fullPath, tPath); + + Buffer tBuffer = fileToBuffer(fullPath); + SDL_RWops* rwOps = SDL_RWFromConstMem(tBuffer.mData, tBuffer.mLength); + gPrismWindowsSoundData.mTrackChunk = Mix_LoadMUS_RW(rwOps, 1); + if (!gPrismWindowsSoundData.mTrackChunk) { + logErrorFormat("Unable to play sound %s: %s", tPath, SDL_GetError()); + } + gPrismWindowsSoundData.mHasLoadedTrack = 1; + } + + static void unloadTrack() { + assert(gPrismWindowsSoundData.mHasLoadedTrack); + + Mix_FreeMusic(gPrismWindowsSoundData.mTrackChunk); + gPrismWindowsSoundData.mHasLoadedTrack = 0; + } + + static void streamMusicFileGeneral(const char* tPath, int tLoopAmount); + + static void playTrackGeneral(int tTrack, int tLoopAmount) { +#ifdef __EMSCRIPTEN__ + return; +#endif + + if (gPrismWindowsSoundData.mIsPlayingTrack) stopTrack(); + if (gPrismWindowsSoundData.mHasLoadedTrack) unloadTrack(); + + char path[1024]; + sprintf(path, "tracks/%d.wav", tTrack); + streamMusicFileGeneral(path, tLoopAmount); + } + + void playTrack(int tTrack) { + playTrackGeneral(tTrack, -1); + } + + void stopTrack() + { + if (!gPrismWindowsSoundData.mIsPlayingTrack) return; + + Mix_HaltMusic(); + } + + void pauseTrack() + { + if (!gPrismWindowsSoundData.mIsPlayingTrack || gPrismWindowsSoundData.mIsPaused) return; + Mix_PauseMusic(); + gPrismWindowsSoundData.mIsPaused = 1; + } + + void resumeTrack() + { + if (!gPrismWindowsSoundData.mIsPlayingTrack || !gPrismWindowsSoundData.mIsPaused) return; + + Mix_ResumeMusic(); + gPrismWindowsSoundData.mIsPaused = 0; + } + + void playTrackOnce(int tTrack) + { + playTrackGeneral(tTrack, 0); + } + + static void musicFinishedCB() { + gPrismWindowsSoundData.mIsPlayingTrack = 0; + + Mix_FreeMusic(gPrismWindowsSoundData.mTrackChunk); + gPrismWindowsSoundData.mHasLoadedTrack = 0; + } + + static void streamMusicFileGeneral(const char* tPath, int tLoopAmount) { + playMusicPath(tPath); + Mix_HookMusicFinished(musicFinishedCB); + + Mix_PlayMusic(gPrismWindowsSoundData.mTrackChunk, tLoopAmount); + gPrismWindowsSoundData.mTimeWhenMusicPlaybackStarted = SDL_GetTicks(); + + gPrismWindowsSoundData.mIsPaused = 0; + gPrismWindowsSoundData.mIsPlayingTrack = 1; + } + + void streamMusicFile(const char* tPath) + { + streamMusicFileGeneral(tPath, -1); + } + + void streamMusicFileOnce(const char* tPath) + { + streamMusicFileGeneral(tPath, 0); + } + + void stopStreamingMusicFile() + { + stopTrack(); + } + + uint64_t getStreamingSoundTimeElapsedInMilliseconds() + { + if (!gPrismWindowsSoundData.mIsPlayingTrack) return 0; + if (!gPrismWindowsSoundData.mTimeWhenMusicPlaybackStarted) return 0; + + uint64_t now = SDL_GetTicks(); + return (uint64_t)(now - gPrismWindowsSoundData.mTimeWhenMusicPlaybackStarted); + } + + int isPlayingStreamingMusic() + { + return gPrismWindowsSoundData.mIsPlayingTrack; + } + + void stopMusic() + { + stopTrack(); + } + + void pauseMusic() + { + pauseTrack(); + } + + void resumeMusic() + { + resumeTrack(); + } + + + static void microphoneCB(void* userdata, Uint8* stream, int len) { + (void)userdata; + + int i; + for (i = 0; i < len; i++) { + gPrismWindowsSoundData.mMicrophone.mSamplePointer = (gPrismWindowsSoundData.mMicrophone.mSamplePointer + 1) % MICROPHONE_SAMPLE_AMOUNT; + + if (gPrismWindowsSoundData.mMicrophone.mSampleAmount > MICROPHONE_SAMPLE_AMOUNT) { + gPrismWindowsSoundData.mMicrophone.mSampleSum -= gPrismWindowsSoundData.mMicrophone.mSamples[gPrismWindowsSoundData.mMicrophone.mSamplePointer]; + gPrismWindowsSoundData.mMicrophone.mSampleAmount--; + } + + gPrismWindowsSoundData.mMicrophone.mSamples[gPrismWindowsSoundData.mMicrophone.mSamplePointer] = stream[i]; + gPrismWindowsSoundData.mMicrophone.mSampleSum += stream[i]; + gPrismWindowsSoundData.mMicrophone.mSampleAmount++; + } + + gPrismWindowsSoundData.mMicrophone.mMasterPeakVolume = 0; + for (i = 0; i < MICROPHONE_SAMPLE_AMOUNT; i++) { + gPrismWindowsSoundData.mMicrophone.mMasterPeakVolume = max(gPrismWindowsSoundData.mMicrophone.mMasterPeakVolume, (int)gPrismWindowsSoundData.mMicrophone.mSamples[i]); + } + } + + + static void startMicrophone(void* tData) + { + (void)tData; + setProfilingSectionMarkerCurrentFunction(); + SDL_AudioSpec want, have; + + SDL_zero(want); + want.freq = 44100; + want.format = AUDIO_U8; + want.channels = 1; + want.samples = 256; + want.callback = microphoneCB; + gPrismWindowsSoundData.mMicrophone.mMicrophone = SDL_OpenAudioDevice(SDL_GetAudioDeviceName(0, 1), 1, &want, &have, 0); + logg("Opening audio device"); + logString(SDL_GetAudioDeviceName(0, 1)); + if (have.format != want.format) { + logError("We didn't get the wanted format."); + recoverFromError(); + return; + } + + SDL_PauseAudioDevice(gPrismWindowsSoundData.mMicrophone.mMicrophone, 0); + + gPrismWindowsSoundData.mMicrophone.mSampleSum = 0; + gPrismWindowsSoundData.mMicrophone.mSamplePointer = 0; + gPrismWindowsSoundData.mMicrophone.mSampleAmount = 0; + gPrismWindowsSoundData.mMicrophone.mIsMicrophoneActive = 1; + gPrismWindowsSoundData.mMicrophone.mMasterPeakVolume = 0; + } + + static void stopMicrophone(void* tData) + { + (void)tData; + setProfilingSectionMarkerCurrentFunction(); + SDL_CloseAudioDevice(gPrismWindowsSoundData.mMicrophone.mMicrophone); + + gPrismWindowsSoundData.mMicrophone.mIsMicrophoneActive = 0; + } + + ActorBlueprint getMicrophoneHandlerActorBlueprint() + { + return makeActorBlueprint(startMicrophone, stopMicrophone); + } + + double getMicrophoneVolume() + { + return gPrismWindowsSoundData.mMicrophone.mMasterPeakVolume / 255.0; + } +} \ No newline at end of file diff --git a/linux/soundeffect_linux.cpp b/linux/soundeffect_linux.cpp new file mode 100644 index 0000000..229f6ce --- /dev/null +++ b/linux/soundeffect_linux.cpp @@ -0,0 +1,205 @@ +#include "prism/soundeffect.h" + +#include +#include +#include +#include "prism/file.h" +#include "prism/sound.h" +#include "prism/datastructures.h" +#include "prism/memoryhandler.h" +#include "prism/stlutil.h" +#include +#include "prism/windows/debugimgui_win.h" + +using namespace std; +namespace prism { + + typedef struct { + Buffer mBuffer; + } SoundEffectEntry; + + static struct { + double mVolume; + map mAllocatedChunks; + map mChunks; + } gSoundEffectData; + +#ifdef _WIN32 + static void imguiChunks() + { + if (ImGui::TreeNode("Chunks")) + { + for (auto& e : gSoundEffectData.mChunks) + { + ImGui::Text("ID: %d", e.first); ImGui::SameLine(); + ImGui::Text("Address: %p", e.second); + } + ImGui::TreePop(); + } + } + + static void imguiAllocatedChunks() + { + if (ImGui::TreeNode("Allocated Chunks")) + { + for (auto& e : gSoundEffectData.mAllocatedChunks) + { + ImGui::Text("ID: %d", e.first); ImGui::SameLine(); + ImGui::Text("Size: %d", e.second.mBuffer.mLength); + } + ImGui::TreePop(); + } + + } + + static void imguiSoundEffectData() { + ImGui::Text("Volume: %f", gSoundEffectData.mVolume); + imguiAllocatedChunks(); + imguiChunks(); + } + + void imguiSoundEffectsHardware() { + static bool isWindowShown = false; + imguiPrismAddTab("Prism", "SoundEffects HW", &isWindowShown); + if (isWindowShown) + { + ImGui::Begin("SoundEffects HW", &isWindowShown); + imguiSoundEffectData(); + ImGui::End(); + } + } +#endif + + void initSoundEffects() { + gSoundEffectData.mVolume = 20; + } + + void setupSoundEffectHandler() { + gSoundEffectData.mAllocatedChunks.clear(); + gSoundEffectData.mChunks.clear(); + } + + static void unloadSoundEffectEntry(SoundEffectEntry* e) { + freeBuffer(e->mBuffer); + } + + static int unloadSingleSoundEffect(void* tCaller, SoundEffectEntry& tData) { + (void)tCaller; + SoundEffectEntry* e = &tData; + unloadSoundEffectEntry(e); + return 1; + } + + static int unloadSingleChunkEntry(void* tCaller, Mix_Chunk*& tData) { + (void)tCaller; + Mix_FreeChunk(tData); + return 1; + } + + void shutdownSoundEffectHandler() { + stl_int_map_remove_predicate(gSoundEffectData.mAllocatedChunks, unloadSingleSoundEffect); + gSoundEffectData.mAllocatedChunks.clear(); + + stl_int_map_remove_predicate(gSoundEffectData.mChunks, unloadSingleChunkEntry); + gSoundEffectData.mChunks.clear(); + } + + void setSoundEffectCompression(int /*tIsEnabled*/) {} // no need for compression in web/windows + + static int addBufferToSoundEffectHandler(Buffer tBuffer) { + SoundEffectEntry e; + e.mBuffer = tBuffer; + return stl_int_map_push_back(gSoundEffectData.mAllocatedChunks, e); + } + + int loadSoundEffect(const char* tPath) { + Buffer b = fileToBuffer(tPath); + return addBufferToSoundEffectHandler(b); + } + + static int gDummy; + + int loadSoundEffectFromBuffer(const Buffer& tBuffer) { + + Buffer ownedBuffer = copyBuffer(tBuffer); + return addBufferToSoundEffectHandler(ownedBuffer); + } + + void unloadSoundEffect(int tID) { + SoundEffectEntry* e = &gSoundEffectData.mAllocatedChunks[tID]; + unloadSoundEffectEntry(e); + gSoundEffectData.mAllocatedChunks.erase(tID); + } + + static void tryEraseChannelChunk(int tChannel) { + setProfilingSectionMarkerCurrentFunction(); + if (stl_map_contains(gSoundEffectData.mChunks, tChannel)) { + Mix_FreeChunk(gSoundEffectData.mChunks[tChannel]); + gSoundEffectData.mChunks.erase(tChannel); + } + } + + int playSoundEffect(int tID) { + setProfilingSectionMarkerCurrentFunction(); + return playSoundEffectChannel(tID, -1, getSoundEffectVolume()); + } + + static int parseVolume(double tVolume) { + return (int)(tVolume * 128); + } + + int playSoundEffectChannel(int tID, int tChannel, double tVolume, double /*tFreqMul*/, int tIsLooping) + { + setProfilingSectionMarkerCurrentFunction(); + SoundEffectEntry* e = &gSoundEffectData.mAllocatedChunks[tID]; + SDL_RWops* rwOps = SDL_RWFromConstMem(e->mBuffer.mData, e->mBuffer.mLength); + Mix_Chunk* chunk = Mix_LoadWAV_RW(rwOps, 0); + int channel = Mix_PlayChannel(tChannel, chunk, tIsLooping); + Mix_Volume(channel, parseVolume(tVolume)); + tryEraseChannelChunk(channel); + + gSoundEffectData.mChunks[channel] = chunk; + return channel; + } + + void stopSoundEffect(int tChannel) { + setProfilingSectionMarkerCurrentFunction(); + Mix_HaltChannel(tChannel); + tryEraseChannelChunk(tChannel); + } + + static void stopSingleSoundEffectCB(int tChannel, Mix_Chunk*& /*tChunk*/) { + setProfilingSectionMarkerCurrentFunction(); + Mix_HaltChannel(tChannel); + tryEraseChannelChunk(tChannel); + } + + void stopAllSoundEffects() { + setProfilingSectionMarkerCurrentFunction(); + stl_int_map_map(gSoundEffectData.mChunks, stopSingleSoundEffectCB); + } + + void panSoundEffect(int tChannel, double tPanning) + { + setProfilingSectionMarkerCurrentFunction(); + tPanning = (tPanning + 1.0) * 0.5; // [-1, 1] --> [0, 1] + const uint8_t right = uint8_t(std::min(std::max(tPanning, 0.0), 1.0) * 255); + Mix_SetPanning(tChannel, 255 - right, right); + } + + int isSoundEffectPlayingOnChannel(int tChannel) { + setProfilingSectionMarkerCurrentFunction(); + return Mix_Playing(tChannel); + } + + double getSoundEffectVolume() { + return gSoundEffectData.mVolume; + } + + void setSoundEffectVolume(double tVolume) { + setProfilingSectionMarkerCurrentFunction(); + gSoundEffectData.mVolume = tVolume; + Mix_Volume(-1, parseVolume(gSoundEffectData.mVolume)); + } + +} \ No newline at end of file diff --git a/linux/system_linux.cpp b/linux/system_linux.cpp new file mode 100644 index 0000000..957544b --- /dev/null +++ b/linux/system_linux.cpp @@ -0,0 +1,348 @@ +#include "prism/system.h" + +#include +#include +#include +#include + +#include +#include +#define GL3_PROTOTYPES 1 +#include +#include + +#include + +#include "prism/windows/debugimgui_win.h" + +#include "prism/log.h" +#include "prism/geometry.h" +#include "prism/math.h" +#include "prism/wrapper.h" +#include "prism/profiling.h" +#include "prism/input.h" + +namespace prism { + + void abortSystem() { + assert(0); + exit(0); + } + + static struct { + + int mIsLoaded; + int mIsExitDisabled; + + int mScreenSizeX; + int mScreenSizeY; + + int mDisplayedWindowSizeX; + int mDisplayedWindowSizeY; + + char mGameName[100]; + } gPrismWindowsSystemData; + +#ifdef _WIN32 +#include +#include "prism/windows/debugimgui_win.h" + + void imguiSystem() + { + static bool isWindowShown = false; + imguiPrismAddTab("Prism", "System", &isWindowShown); + if (isWindowShown) + { + ImGui::Begin("System", &isWindowShown); + ImGui::Text("IsLoaded = %d", gPrismWindowsSystemData.mIsLoaded); + ImGui::Text("IsExitDisabled = %d", gPrismWindowsSystemData.mIsExitDisabled); + ImGui::Text("Screen Size = %d/%d", gPrismWindowsSystemData.mScreenSizeX, gPrismWindowsSystemData.mScreenSizeY); + ImGui::Text("Displayed Screen Size = %d/%d", gPrismWindowsSystemData.mDisplayedWindowSizeX, gPrismWindowsSystemData.mDisplayedWindowSizeY); + ImGui::Text("Game Name = %s", gPrismWindowsSystemData.mGameName); + ImGui::End(); + + } + } +#endif + + SDL_Window* gSDLWindow; + SDL_GLContext gGLContext; + + static void initScreenDefault() { + gPrismWindowsSystemData.mIsLoaded = 1; + gPrismWindowsSystemData.mIsExitDisabled = 0; + gPrismWindowsSystemData.mScreenSizeX = gPrismWindowsSystemData.mDisplayedWindowSizeX = 640; + gPrismWindowsSystemData.mScreenSizeY = gPrismWindowsSystemData.mDisplayedWindowSizeY = 480; + } + + void setGameName(const char* tName) { + strcpy(gPrismWindowsSystemData.mGameName, tName); + } + + void updateGameName(const char* tName) + { + strcpy(gPrismWindowsSystemData.mGameName, tName); + SDL_SetWindowTitle(gSDLWindow, gPrismWindowsSystemData.mGameName); + } + + void setIcon(const char* tPath) + { + SDL_Surface* icon = IMG_Load(tPath); + SDL_SetWindowIcon(gSDLWindow, icon); + SDL_FreeSurface(icon); + } + +#ifdef _WIN32 +#include +#include +#endif + + static void setToProgramDirectory() { +#ifdef _WIN32 + TCHAR wbuf[1024]; + GetModuleFileName(NULL, wbuf, 1024); + + wchar_t* end = wcsrchr(wbuf, '\\'); + end[1] = '\0'; + + _wchdir(wbuf); +#endif + } + + extern void setDrawingScreenScale(double tScaleX, double tScaleY); + + static void setWindowSize(int tX, int tY) { + ScreenSize sz = getScreenSize(); + double scaleX = tX / (double)sz.x; + double scaleY = tY / (double)sz.y; + + scaleX = fmin(scaleX, scaleY); + scaleY = fmin(scaleX, scaleY); + + setDrawingScreenScale(scaleX, scaleY); + gPrismWindowsSystemData.mDisplayedWindowSizeX = (int)(scaleX * sz.x); + gPrismWindowsSystemData.mDisplayedWindowSizeY = (int)(scaleY * sz.y); + SDL_SetWindowSize(gSDLWindow, gPrismWindowsSystemData.mDisplayedWindowSizeX, gPrismWindowsSystemData.mDisplayedWindowSizeY); + } + + static void initOpenGL() { + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); + + gGLContext = SDL_GL_CreateContext(gSDLWindow); + } + + static void initGlew() { + glewExperimental = GL_TRUE; + glewInit(); + } + + void initSystem() { + + setToProgramDirectory(); + SDL_Init(SDL_INIT_EVERYTHING); + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); + + if (gPrismWindowsSystemData.mGameName[0] == '\0') { + sprintf(gPrismWindowsSystemData.mGameName, "Unnamed libtari game port"); + } + gSDLWindow = SDL_CreateWindow(gPrismWindowsSystemData.mGameName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL); + + initOpenGL(); + + initGlew(); + } + + void shutdownSystem() { + SDL_GL_DeleteContext(gGLContext); + + SDL_DestroyWindow(gSDLWindow); + + IMG_Quit(); + SDL_Quit(); + } + + static void resizeWindow(SDL_Event* e) { + setDisplayedScreenSize(e->window.data1, e->window.data2); + } + static void checkWindowEvents(SDL_Event* e) { + if (e->window.event == SDL_WINDOWEVENT_RESIZED) { + resizeWindow(e); + } + + } + + Vector3D correctSDLWindowPosition(const Vector3D& v) { + ScreenSize sz = getScreenSize(); + double scaleX = gPrismWindowsSystemData.mDisplayedWindowSizeX / (double)sz.x; + double scaleY = gPrismWindowsSystemData.mDisplayedWindowSizeY / (double)sz.y; + return vecScale3D(v, Vector3D(1 / scaleX, 1 / scaleY, 1)); + } + + static void switchFullscreen() { + const auto flags = SDL_GetWindowFlags(gSDLWindow); + setScreenFullscreen(!(flags & SDL_WINDOW_FULLSCREEN)); + } + + static void checkFullscreen() { + const auto webInput = isOnWeb() && hasPressedKeyboardKeyFlank(KEYBOARD_F8_PRISM); + const auto nonWebInput = !isOnWeb() && hasPressedKeyboardMultipleKeyFlank(2, KEYBOARD_ALT_LEFT_PRISM, KEYBOARD_RETURN_PRISM); + if (webInput || nonWebInput) { + switchFullscreen(); + } + } + + void setScreenPosition(int tX, int tY) + { +#ifdef _WIN32 + SDL_SetWindowPosition(gSDLWindow, tX, tY); +#endif + } + + void setScreenFullscreen(bool tIsFullscreen) { + if (tIsFullscreen) { + if (isOnWeb()) { + SDL_SetWindowFullscreen(gSDLWindow, SDL_WINDOW_FULLSCREEN_DESKTOP); // does not break window resizing, unlike the other one, probably due to https://github.com/emscripten-ports/SDL2/issues/8 + } + else { + SDL_SetWindowFullscreen(gSDLWindow, SDL_WINDOW_FULLSCREEN); + } + } + else { + SDL_SetWindowFullscreen(gSDLWindow, 0); + if (isOnWeb()) { + setWindowSize(640, 480); // force real window size after fullscreen switch until (https://github.com/emscripten-ports/SDL2/issues/8) resolved + } + } + } + + extern void receiveCharacterInputFromSDL(const std::string& tText); + + void updateSystem() { + setProfilingSectionMarkerCurrentFunction(); + SDL_Event e; + while (SDL_PollEvent(&e) != 0) { +#ifdef _WIN32 + imguiPrismProcessEvent(&e); +#endif + switch (e.type) { + case SDL_QUIT: + returnToMenu(); + break; + case SDL_WINDOWEVENT: + checkWindowEvents(&e); + break; + case SDL_TEXTINPUT: + receiveCharacterInputFromSDL(e.text.text); + break; + default: + break; + } + } + + checkFullscreen(); + + } + + void setScreen(int tX, int tY, int tFramerate, int tIsVGA) { + (void)tIsVGA; + (void)tFramerate; + if (!gPrismWindowsSystemData.mIsLoaded) initScreenDefault(); + gPrismWindowsSystemData.mScreenSizeX = tX; + gPrismWindowsSystemData.mScreenSizeY = tY; + } + + void setScreenSize(int tX, int tY) { + if (!gPrismWindowsSystemData.mIsLoaded) initScreenDefault(); + + gPrismWindowsSystemData.mScreenSizeX = tX; + gPrismWindowsSystemData.mScreenSizeY = tY; + } + + ScreenSize getScreenSize() { + if (!gPrismWindowsSystemData.mIsLoaded) initScreenDefault(); + ScreenSize ret; + ret.x = gPrismWindowsSystemData.mScreenSizeX; + ret.y = gPrismWindowsSystemData.mScreenSizeY; + return ret; + } + + ScreenSize getDisplayedScreenSize() + { + if (!gPrismWindowsSystemData.mIsLoaded) initScreenDefault(); + ScreenSize ret; + ret.x = gPrismWindowsSystemData.mDisplayedWindowSizeX; + ret.y = gPrismWindowsSystemData.mDisplayedWindowSizeY; + return ret; + } + + void setDisplayedScreenSize(int tX, int tY) + { + setWindowSize(tX, tY); + } + + void setScreenFramerate(int tFramerate) { + (void)tFramerate; + } + + void setVGA() { + + } + + void returnToMenu() { + if (!gPrismWindowsSystemData.mIsExitDisabled) + { + exit(0); + } + } + + int isOnDreamcast() + { + return 0; + } + + int isOnWindows() + { + return 0; + } + + int isOnLinux() + { + return 1; + } + + int isOnWeb() + { + return 0; + } + + int isOnVita() + { + return 0; + } + + void setSystemExitDisabled(int tIsExitDisabled) + { + gPrismWindowsSystemData.mIsExitDisabled = tIsExitDisabled; + } + + uint64_t getSystemTicks() { + return SDL_GetTicks(); + } + + uint64_t getUnixTimestampSeconds() + { + return std::time(0); + } + + uint64_t getUnixTimestampMilliseconds() + { + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } + +} \ No newline at end of file diff --git a/linux/texture_linux.cpp b/linux/texture_linux.cpp new file mode 100644 index 0000000..b25e437 --- /dev/null +++ b/linux/texture_linux.cpp @@ -0,0 +1,334 @@ +#include "prism/texture.h" + +#include +#include +#include + +#include +#include + +#include "prism/file.h" +#include "prism/log.h" +#include "prism/memoryhandler.h" +#include "prism/system.h" +#include "prism/math.h" +#include "prism/compression.h" + +namespace prism { + + static TextureData textureFromSurface(SDL_Surface* tSurface) { + TextureData returnData; + returnData.mTexture = allocTextureMemory(sizeof(GLTextureData)); + returnData.mTextureSize.x = tSurface->w; + returnData.mTextureSize.y = tSurface->h; + returnData.mHasPalette = 0; + Texture texture = (Texture)returnData.mTexture->mData; + + auto surface = SDL_ConvertSurfaceFormat(tSurface, SDL_PIXELFORMAT_RGBA32, 0); + SDL_FreeSurface(tSurface); + + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &texture->mTexture); + glBindTexture(GL_TEXTURE_2D, texture->mTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surface->w, surface->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surface->pixels); + glBindTexture(GL_TEXTURE_2D, last_texture); + + SDL_FreeSurface(surface); + + return returnData; + } + + TextureData loadTexturePNG(const char* tFileDir) { + + Buffer b = fileToBuffer(tFileDir); + SDL_RWops* memStream = SDL_RWFromMem(b.mData, b.mLength); + SDL_Surface* loadedSurface = IMG_Load_RW(memStream, 1); + freeBuffer(b); + if (loadedSurface == NULL) + { + logError("Unable to load file:"); + logErrorString(tFileDir); + logErrorString(IMG_GetError()); + abortSystem(); + } + + return textureFromSurface(loadedSurface); + } + + + +#define TWIDTAB(x) ( (x&1)|((x&2)<<1)|((x&4)<<2)|((x&8)<<3)|((x&16)<<4)| \ + ((x&32)<<5)|((x&64)<<6)|((x&128)<<7)|((x&256)<<8)|((x&512)<<9) ) +#define TWIDOUT(x, y) ( TWIDTAB((y)) | (TWIDTAB((x)) << 1) ) +#define MIN(a, b) ( (a)<(b)? (a):(b) ) + + + static void untwiddle(uint16_t* src, uint16_t* output, int w, int h) { + int min = MIN(w, h); + int mask = min - 1; + uint16_t* pixels = output; + uint16_t* vtex = src; + int x, y, yout; + + for (y = 0; y < h; y++) { + yout = y; + + for (x = 0; x < w; x++) { + pixels[y * w + x] = vtex[TWIDOUT(x & mask, yout & mask) + + (x / min + yout / min) * min * min]; + } + } + } + + typedef struct kmg_header { + uint32_t magic; /* Magic code */ + uint32_t version; /* Version code */ + uint32_t platform; /* Platform specifier (major format) */ + uint32_t format; /* Image (minor) format spec */ + uint32_t width; /* Image width */ + uint32_t height; /* Image height */ + uint32_t byte_count; /* Image's data size in bytes */ + uint8_t padding[36]; /* Pad to a 64-byte header (all zeros) */ + } KMGHeader; + +#define KMG_MAGIC 0x00474d4b /* 'KMG\0' */ + + + static SDL_Surface* makeSurfaceFromUntwiddledTexture(const Buffer& b, const KMGHeader& tHeader) { + + uint32_t rmask = 0x0f00; + uint32_t gmask = 0x00f0; + uint32_t bmask = 0x000f; + uint32_t amask = 0xf000; + int depth = 16; + int pitch = 2 * tHeader.width; + + return SDL_CreateRGBSurfaceFrom(b.mData, tHeader.width, tHeader.height, depth, pitch, rmask, gmask, bmask, amask); + } + + static Buffer untwiddleBuffer(const Buffer& tBuffer, uint32_t tWidth, uint32_t tHeight) { + uint16_t* dst = (uint16_t*)allocMemory(tBuffer.mLength); + uint32_t dstLength = tBuffer.mLength; + + untwiddle((uint16_t*)tBuffer.mData, dst, tWidth, tHeight); + + return makeBufferOwned(dst, dstLength); + } + + static KMGHeader untwiddleKMGBufferAndReturnHeader(Buffer* tBuffer) { + Buffer src = *tBuffer; + Buffer dst = *tBuffer; + + BufferPointer p = getBufferPointer(src); + KMGHeader hdr; + readFromBufferPointer(&hdr, &p, sizeof hdr); + + dst.mLength = dst.mLength - sizeof hdr; + dst.mData = allocMemory(dst.mLength); + + if (hdr.magic != KMG_MAGIC) { + logError("Unable to open KMG file."); + logErrorHex(hdr.magic); + abortSystem(); + } + + untwiddle((uint16_t*)p, (uint16_t*)dst.mData, hdr.width, hdr.height); + + freeBuffer(src); + *tBuffer = dst; + + return hdr; + } + + static TextureData loadTexturePKGWindows(const char* tFileDir) { + Buffer b = fileToBuffer(tFileDir); + decompressBufferZSTD(&b); + KMGHeader hdr = untwiddleKMGBufferAndReturnHeader(&b); + SDL_Surface* s = makeSurfaceFromUntwiddledTexture(b, hdr); + return textureFromSurface(s); + } + + TextureData loadTexturePKG(const char* tFileDir) { + char pngPath[1024]; + + strcpy(pngPath, tFileDir); + int len = int(strlen(pngPath)); + pngPath[len - 2] = 'n'; + + + char fullFileName[1024]; + char fullFileNamePNG[1024]; + getFullPath(fullFileName, tFileDir); + getFullPath(fullFileNamePNG, pngPath); + + if (isFile(pngPath)) { + return loadTexturePNG(fullFileNamePNG); + } + else { + return loadTexturePKGWindows(fullFileName); + } + + + } + + TextureData loadTexture(const char* tFileDir) { + const char* fileExt = getFileExtension(tFileDir); + + if (!strcmp("pkg", fileExt)) { + return loadTexturePKG(tFileDir); + } + else { + logError("Unable to identify texture file type."); + logErrorString(fileExt); + abortSystem(); + TextureData ret; + ret.mHasPalette = 0; + return ret; + } + } + + void unloadTexture(TextureData& tTexture) { + freeTextureMemory(tTexture.mTexture); + } + + int getTextureHash(const TextureData& tTexture) { + return (long int)tTexture.mTexture; + } + + int canLoadTexture(const char* tPath) { + const char* fileExt = getFileExtension(tPath); + + if (!strcmp("pkg", fileExt)) { + char path[1024]; + strcpy(path, tPath); + char* newFileExt = getFileExtension(path); + strcpy(newFileExt, "png"); + return isFile(path) || isFile(tPath); + } + + return 0; + } + + TextureData loadTextureFromARGB16Buffer(const Buffer& b, int tWidth, int tHeight) + { + uint32_t amask = 0x0000f000; + uint32_t rmask = 0x00000f00; + uint32_t gmask = 0x000000f0; + uint32_t bmask = 0x0000000f; + + int depth = 16; + int pitch = 2 * tWidth; + + SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(b.mData, tWidth, tHeight, depth, pitch, rmask, gmask, bmask, amask); + return textureFromSurface(surface); + } + + TextureData loadTextureFromTwiddledARGB16Buffer(const Buffer& b, int tWidth, int tHeight) { + Buffer untwiddled = untwiddleBuffer(b, (uint32_t)tWidth, (uint32_t)tHeight); + TextureData ret = loadTextureFromARGB16Buffer(untwiddled, tWidth, tHeight); + freeBuffer(untwiddled); + return ret; + } + + TextureData loadTextureFromARGB32Buffer(const Buffer& b, int tWidth, int tHeight) { + uint32_t amask = 0xff000000; + uint32_t rmask = 0x00ff0000; + uint32_t gmask = 0x0000ff00; + uint32_t bmask = 0x000000ff; + + int depth = 32; + int pitch = 4 * tWidth; + + SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(b.mData, tWidth, tHeight, depth, pitch, rmask, gmask, bmask, amask); + return textureFromSurface(surface); + } + + TextureData loadTextureFromRawPNGBuffer(const Buffer& b, int tWidth, int tHeight) { + (void)tWidth; + (void)tHeight; + SDL_RWops* memStream = SDL_RWFromMem(b.mData, b.mLength); + if (!memStream) { + logError("Unable to create memory stream."); + logErrorString(SDL_GetError()); + abortSystem(); + } + + SDL_Surface* surface = IMG_LoadPNG_RW(memStream); + if (!surface) { + logError("Unable to create surface."); + logErrorString(SDL_GetError()); + abortSystem(); + } + + return textureFromSurface(surface); + } + + TextureData loadPalettedTextureFrom8BitBuffer(const Buffer& b, int tPaletteID, int tWidth, int tHeight) { + TextureData returnData; + returnData.mTexture = allocTextureMemory(sizeof(GLTextureData)); + returnData.mTextureSize.x = tWidth; + returnData.mTextureSize.y = tHeight; + returnData.mHasPalette = 1; + returnData.mPaletteID = tPaletteID; + Texture texture = (Texture)returnData.mTexture->mData; + + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &texture->mTexture); + glBindTexture(GL_TEXTURE_2D, texture->mTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, tWidth, tHeight, 0, GL_ALPHA, GL_UNSIGNED_BYTE, b.mData); + glBindTexture(GL_TEXTURE_2D, last_texture); + + return returnData; + } + + static std::string getSystemFontFile(const std::string& tFaceName) { + return "/usr/share/fonts/" + tFaceName; + } + + TruetypeFont loadTruetypeFont(const char* tName, double tSize) + { + char path[1024]; + if (isFile(tName)) { + getFullPath(path, tName); + } + else { + const auto systemPath = getSystemFontFile(tName); + strcpy(path, systemPath.c_str()); + if (!isFile(path)) { + logError("Unable to open font file."); + logErrorString(tName); + logErrorString(path); + abortSystem(); + } + } + + TTF_Font* font = TTF_OpenFont(path, (int)tSize); + + return font; + } + + void unloadTruetypeFont(TruetypeFont tFont) + { + TTF_Font* font = (TTF_Font*)tFont; + TTF_CloseFont(font); + } + + typedef unsigned char BYTE; + + void saveScreenShot(const char* tFileDir) { + const auto sz = getDisplayedScreenSize(); + std::vector pixels(3 * sz.x * sz.y); + glReadPixels(0, 0, sz.x, sz.y, GL_RGB, GL_UNSIGNED_BYTE, pixels.data()); + saveRGB32ToPNG(makeBuffer(pixels.data(), uint32_t(pixels.size())), sz.x, sz.y, tFileDir); + } +} \ No newline at end of file diff --git a/linux/thread_linux.cpp b/linux/thread_linux.cpp new file mode 100644 index 0000000..de62e6e --- /dev/null +++ b/linux/thread_linux.cpp @@ -0,0 +1,98 @@ +#include "prism/thread.h" + +#include +#include +#include + +#include "prism/datastructures.h" +#include "prism/memoryhandler.h" + +namespace prism { + + typedef struct { + int mID; + pthread_t mThreadID; + void(*mFunc)(void*); + void* mCaller; + } ThreadData; + + static struct { + IntMap mThreads; + sem_t mThreadMapAccessSemaphore; + } gPrismLinuxThreadData; + + void initThreading() { + sem_init(&gPrismLinuxThreadData.mThreadMapAccessSemaphore, 0, 1); + gPrismLinuxThreadData.mThreads = new_int_map(); + } + + static int forceShutdownSingleThread(void* tCaller, void* tData) { + (void)tCaller; + ThreadData* e = (ThreadData*)tData; + + pthread_cancel(e->mThreadID); + + return 1; + } + + void shutdownThreading() + { + sem_wait(&gPrismLinuxThreadData.mThreadMapAccessSemaphore); + int_map_remove_predicate(&gPrismLinuxThreadData.mThreads, forceShutdownSingleThread, NULL); + delete_int_map(&gPrismLinuxThreadData.mThreads); + sem_post(&gPrismLinuxThreadData.mThreadMapAccessSemaphore); + sem_destroy(&gPrismLinuxThreadData.mThreadMapAccessSemaphore); + } + + void* threadFunction(void* lpParam) { + ThreadData* e = (ThreadData*)lpParam; + + e->mFunc(e->mCaller); + + sem_wait(&gPrismLinuxThreadData.mThreadMapAccessSemaphore); + int_map_remove(&gPrismLinuxThreadData.mThreads, e->mID); + sem_post(&gPrismLinuxThreadData.mThreadMapAccessSemaphore); + + return NULL; + } + + int startThread(void(tFunc)(void*), void* tCaller) + { + ThreadData* e = (ThreadData*)allocMemory(sizeof(ThreadData)); + e->mFunc = tFunc; + e->mCaller = tCaller; + + sem_wait(&gPrismLinuxThreadData.mThreadMapAccessSemaphore); + e->mID = int_map_push_back_owned(&gPrismLinuxThreadData.mThreads, e); + sem_post(&gPrismLinuxThreadData.mThreadMapAccessSemaphore); + + pthread_create(&e->mThreadID, NULL, threadFunction, e); + return e->mID; + } + + sem_t createSemaphore(int tInitialAccessesAllowed) + { + sem_t sem; + sem_init(&sem, 0, tInitialAccessesAllowed); + return sem; + } + + void destroySemaphore(sem_t tSemaphore) + { + sem_destroy(&tSemaphore); + } + + void lockSemaphore(sem_t tSemaphore) + { + sem_wait(&tSemaphore); + } + + void releaseSemaphore(sem_t tSemaphore) + { + sem_post(&tSemaphore); + } + + void terminateSelfAsThread(int /*tReturnValue*/) { + pthread_exit(NULL); + } +} \ No newline at end of file diff --git a/memoryhandler.cpp b/memoryhandler.cpp index fc03ab1..7c3f8bd 100644 --- a/memoryhandler.cpp +++ b/memoryhandler.cpp @@ -17,12 +17,17 @@ #include "prism/windows/debugimgui_win.h" #endif +#ifdef __linux__ +#include "imgui/imgui.h" +#include "prism/windows/debugimgui_win.h" +#endif + #ifdef DREAMCAST #include #endif -#if defined _WIN32 || defined __EMSCRIPTEN__ +#if defined _WIN32 || defined __EMSCRIPTEN__ || defined __linux__ #include #include @@ -63,7 +68,7 @@ namespace prism { #define virtualizeTextureHW virtualizeTextureDreamcast #define unvirtualizeTextureHW unvirtualizeTextureDreamcast -#elif defined _WIN32 || defined __EMSCRIPTEN__ +#elif defined _WIN32 || defined __EMSCRIPTEN__ || defined __linux__ void* allocGLTexture(size_t) { GLTextureData* data = (GLTextureData*)malloc(sizeof(GLTextureData)); return data; @@ -200,7 +205,7 @@ namespace prism { int mActive; } gMemoryHandler; -#ifdef _WIN32 +#if defined(_WIN32) || defined(__linux__) static std::string_view allocationStrategyToString(const AllocationStrategy& allocationStrategy) { if (allocationStrategy.mMalloc == malloc) return "Hash Map Strategy Main Memory"; @@ -213,8 +218,8 @@ namespace prism { { GLTextureData* data = (GLTextureData*)tTextureMemory->mData; ImGui::Text("%d", data->mTexture); - ImGui::Text("%d", tTextureMemory->mSize); - ImGui::Text("%d", tTextureMemory->mIsCompressed ? tTextureMemory->mCompressedSize : 0); + ImGui::Text("%ld", tTextureMemory->mSize); + ImGui::Text("%ld", tTextureMemory->mIsCompressed ? tTextureMemory->mCompressedSize : 0); ImGui::Text("%d", tTextureMemory->mIsVirtual); ImGui::Text("%d", tTextureMemory->mIsCompressed); ImGui::TreePop(); @@ -226,8 +231,8 @@ namespace prism { GLTextureData* data = (GLTextureData*)tTextureMemory->mData; ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("%d", data->mTexture); ImGui::TableNextColumn(); - ImGui::Text("%d", tTextureMemory->mSize); ImGui::TableNextColumn(); - ImGui::Text("%d", tTextureMemory->mIsCompressed ? tTextureMemory->mCompressedSize : 0); ImGui::TableNextColumn(); + ImGui::Text("%ld", tTextureMemory->mSize); ImGui::TableNextColumn(); + ImGui::Text("%ld", tTextureMemory->mIsCompressed ? tTextureMemory->mCompressedSize : 0); ImGui::TableNextColumn(); ImGui::Text("%d", tTextureMemory->mIsVirtual); ImGui::TableNextColumn(); ImGui::Text("%d", tTextureMemory->mIsCompressed); } @@ -245,7 +250,7 @@ namespace prism { { if (ImGui::TreeNode(std::to_string(row).c_str())) { - ImGui::Text("Size = %d", tMemoryListStack.mMaps[row].mMap.size()); + ImGui::Text("Size = %ld", tMemoryListStack.mMaps[row].mMap.size()); if (ImGui::TreeNode("Elements")) { static ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg; @@ -260,7 +265,7 @@ namespace prism { { ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::Text("%X", pointer); + ImGui::Text("%p", pointer); } ImGui::EndTable(); } diff --git a/memorypool.cpp b/memorypool.cpp index 22b5f8c..8e0f9b7 100644 --- a/memorypool.cpp +++ b/memorypool.cpp @@ -50,7 +50,7 @@ namespace prism { typedef struct _wof_chunk_hdr_t { int prev; - int allocator; + intptr_t allocator; /* flags */ int last : 1; int used : 1; @@ -393,7 +393,7 @@ namespace prism { extra->prev = chunk->len; extra->used = FALSE; extra->jumbo = FALSE; - extra->allocator = (int)allocator; + extra->allocator = reinterpret_cast(allocator); /* Correctly update the following chunk's back-pointer */ if (!last) { @@ -442,7 +442,7 @@ namespace prism { extra->prev = chunk->len; extra->used = FALSE; extra->jumbo = FALSE; - extra->allocator = (int)allocator; + extra->allocator = reinterpret_cast(allocator); /* Correctly update the following chunk's back-pointer */ if (!last) { @@ -504,7 +504,7 @@ namespace prism { chunk->last = TRUE; chunk->prev = 0; chunk->len = WOF_BLOCK_SIZE - WOF_BLOCK_HEADER_SIZE; - chunk->allocator = (int)allocator; + chunk->allocator = reinterpret_cast(allocator); /* now push that chunk onto the master list */ wof_push_master(allocator, chunk); @@ -555,7 +555,7 @@ namespace prism { chunk->last = TRUE; chunk->used = TRUE; chunk->jumbo = TRUE; - chunk->allocator = (int)allocator; + chunk->allocator = reinterpret_cast(allocator); chunk->len = 0; chunk->prev = 0; @@ -937,7 +937,7 @@ namespace prism { wof_allocator_t* allocator = (wof_allocator_t*)tPool; wof_chunk_hdr_t* chunk; chunk = WOF_DATA_TO_CHUNK(ptr); - return chunk->allocator == (int)allocator; + return chunk->allocator == reinterpret_cast(allocator); } } diff --git a/script.cpp b/script.cpp index 50458ae..0f16817 100644 --- a/script.cpp +++ b/script.cpp @@ -187,7 +187,7 @@ namespace prism { logString(ret.mPointer); logString(tName); recoverFromError(); -#if defined(DREAMCAST) || defined(__EMSCRIPTEN__) +#if defined(DREAMCAST) || defined(__EMSCRIPTEN__) || defined(__linux__) return ret; #endif }