diff --git a/EmuFramework/include/emuframework/VController.hh b/EmuFramework/include/emuframework/VController.hh index 591a90de2..e26dfef80 100755 --- a/EmuFramework/include/emuframework/VController.hh +++ b/EmuFramework/include/emuframework/VController.hh @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -114,7 +115,7 @@ public: } protected: - Gfx::ILitTexQuads spriteQuads; + Gfx::ILitTexFanQuads spriteQuads; Gfx::TextureSpan tex; Gfx::Texture mapImg; WRect padBaseArea, padArea; @@ -187,42 +188,67 @@ public: constexpr VControllerButton(KeyInfo key): key{key} {} void setPos(WPt pos, WRect viewBounds, _2DOrigin = C2DO); void setSize(WSize size, WSize extendedSize = {}); - void setImage(Gfx::RendererTask &, Gfx::TextureSpan, int aR = 1); + void setImage(Gfx::TextureSpan, int aR = 1); WRect bounds() const { return bounds_; } WRect realBounds() const { return extendedBounds_; } - void drawBounds(Gfx::RendererCommands &__restrict__) const; - void drawSprite(Gfx::RendererCommands &__restrict__) const; std::string name(const EmuApp &) const; bool overlaps(WPt windowPos) const { return enabled && realBounds().overlaps(windowPos); } void setAlpha(float alpha); - void updateColor(Gfx::Color c, float alpha) + void setAlpha(float alpha, auto &container) { - color = c; setAlpha(alpha); + container.updateSprite(*this); + } + + void updateColor(Gfx::Color c, float alpha, auto &container) + { + color = c; + setAlpha(alpha, container); + } + + void updateSprite(auto &quads) + { + Gfx::ILitTexQuad({.bounds = bounds_.as(), .color = spriteColor, .textureSpan = texture}).write(quads, spriteIdx); + } + + void updateSprite(auto &quads, auto &boundQuads) + { + updateSprite(quads); + float brightness = isHighlighted ? 2.f : 1.f; + Gfx::IColQuad({.bounds = extendedBounds_.as(), .color = Gfx::Color{.5f}.multiplyRGB(brightness)}).write(boundQuads, spriteIdx); } -protected: - Gfx::ILitTexQuads quad; - Gfx::IQuads boundQuads; Gfx::TextureSpan texture; +protected: Gfx::Color spriteColor; WRect bounds_{}; WRect extendedBounds_{}; int aspectRatio{1}; - - void updateSprite(); public: Gfx::Color color{}; KeyInfo key{}; - bool enabled = true; + uint8_t spriteIdx{}; + bool enabled{true}; bool skipLayout{}; bool isHighlighted{}; }; -class VControllerButtonGroup +template +class BaseVControllerButtonGroup { public: + void drawButtons(Gfx::RendererCommands &__restrict__) const; + void updateSprites(); + void updateSprite(VControllerButton &); + void setAlpha(float alpha); +}; + +class VControllerButtonGroup : public BaseVControllerButtonGroup +{ +public: + friend class BaseVControllerButtonGroup; + struct LayoutConfig { int8_t rowItems{1}; @@ -260,12 +286,11 @@ public: WRect realBounds() const { return bounds() + paddingRect(); } int rows() const; std::array findButtonIndices(WPt windowPos) const; - void drawButtons(Gfx::RendererCommands &__restrict__) const; void drawBounds(Gfx::RendererCommands &__restrict__) const; std::string name(const EmuApp &) const; void updateMeasurements(const Window &win); void transposeKeysForPlayer(const EmuApp &, int player); - void setAlpha(float alpha) { for(auto &b : buttons) { b.setAlpha(alpha); } } + void setTask(Gfx::RendererTask &task) { quads.setTask(task); boundQuads.setTask(task); } static size_t layoutConfigSize() { @@ -285,7 +310,10 @@ public: std::vector buttons; protected: + Gfx::ILitTexQuads quads; + Gfx::IColQuads boundQuads; WRect bounds_; + int enabledBtns{}; int btnSize{}; int spacingPixels{}; int16_t btnStagger{}; @@ -294,9 +322,11 @@ public: LayoutConfig layout{}; }; -class VControllerUIButtonGroup +class VControllerUIButtonGroup : public BaseVControllerButtonGroup { public: + friend class BaseVControllerButtonGroup; + struct LayoutConfig { int8_t rowItems{1}; @@ -320,9 +350,8 @@ public: auto bounds() const { return bounds_; } WRect realBounds() const { return bounds(); } int rows() const; - void drawButtons(Gfx::RendererCommands &__restrict__) const; std::string name(const EmuApp &) const; - void setAlpha(float alpha) { for(auto &b : buttons) { b.setAlpha(alpha); } } + void setTask(Gfx::RendererTask &task) { quads.setTask(task); } static size_t layoutConfigSize() { @@ -337,7 +366,9 @@ public: std::vector buttons; protected: + Gfx::ILitTexQuads quads; WRect bounds_{}; + int enabledBtns{}; int btnSize{}; public: LayoutConfig layout{}; @@ -397,12 +428,6 @@ public: }, *this); } - void draw(Gfx::RendererCommands &__restrict__ cmds, bool showHidden) const - { - drawBounds(cmds, showHidden); - drawButtons(cmds, showHidden); - } - void place(WRect viewBounds, WRect windowBounds, int layoutIdx) { auto &lPos = layoutPos[layoutIdx]; @@ -507,6 +532,15 @@ public: return 1; }, *this); } + + void updateSprite(VControllerButton &b) + { + visit([&](auto &e) + { + if constexpr(requires {e.updateSprite(b);}) + e.updateSprite(b); + }, *this); + } }; enum class VControllerVisibility : uint8_t @@ -549,6 +583,7 @@ public: bool pointerInputEvent(const Input::MotionEvent &, WindowRect gameRect); bool keyInput(const Input::KeyEvent &); void draw(Gfx::RendererCommands &__restrict__, bool showHidden = false); + void draw(Gfx::RendererCommands &__restrict__, const VControllerElement &elem, bool showHidden = false) const; bool isInKeyboardMode() const; void setKeyboardImage(Gfx::TextureSpan img); void setButtonAlpha(std::optional); @@ -581,7 +616,7 @@ public: bool updateAutoOnScreenControlVisible(); bool readConfig(EmuApp &, MapIO &, unsigned key, size_t size); void writeConfig(FileIO &) const; - void configure(Window &, Gfx::Renderer &, const Gfx::GlyphTextureSet &face); + void configure(Window &, Gfx::Renderer &, const Gfx::GlyphTextureSet &face, const Gfx::IndexBuffer &quadIdxs); void resetEmulatedDevicePositions(); void resetEmulatedDeviceGroups(); void resetUIPositions(); @@ -610,6 +645,9 @@ private: const Window *win{}; const WindowData *winData{}; const Gfx::GlyphTextureSet *facePtr{}; + const Gfx::IndexBuffer *quadIdxsPtr{}; + Gfx::IndexBuffer fanQuadIdxs; + Gfx::TextureBinding gamepadTex, uiTex; VControllerKeyboard kb; std::vector gpElements{}; std::vector uiElements{}; @@ -646,6 +684,7 @@ private: int uiButtonPixelSize() const; void writeDeviceButtonsConfig(FileIO &) const; void writeUIButtonsConfig(FileIO &) const; + void setIndexArray(Gfx::RendererCommands &__restrict__, const VControllerElement &) const; }; } diff --git a/EmuFramework/include/emuframework/VideoImageOverlay.hh b/EmuFramework/include/emuframework/VideoImageOverlay.hh index 49816f762..4c68c313a 100644 --- a/EmuFramework/include/emuframework/VideoImageOverlay.hh +++ b/EmuFramework/include/emuframework/VideoImageOverlay.hh @@ -49,7 +49,8 @@ private: glm::i16vec2 pos; glm::vec2 texCoord; }; - using Quads = Gfx::BaseQuads; + using Quad = Gfx::BaseQuad; + using Quads = Gfx::ObjectVertexBuffer; Quads quad; Gfx::Texture texture; diff --git a/EmuFramework/include/emuframework/keyRemappingUtils.hh b/EmuFramework/include/emuframework/keyRemappingUtils.hh index b875a83a5..15d65ae6e 100644 --- a/EmuFramework/include/emuframework/keyRemappingUtils.hh +++ b/EmuFramework/include/emuframework/keyRemappingUtils.hh @@ -49,19 +49,6 @@ constexpr Input::Key genericGamepadKeycodeToOuya(Input::Key k) default: return k; } } - -constexpr Input::Key genericGamepadKeycodeToBitdo(Input::Key k) -{ - using namespace Input; - switch(k) - { - case Keycode::GAME_A: return Keycode::GAME_B; - case Keycode::GAME_B: return Keycode::GAME_A; - case Keycode::GAME_X: return Keycode::GAME_Y; - case Keycode::GAME_Y: return Keycode::GAME_X; - default: return k; - } -} #endif #if defined(__ANDROID__) && __ARM_ARCH == 7 @@ -247,7 +234,6 @@ constexpr std::span genericKeyConfigs() static constexpr auto genericGamepadMap = concatToArrayNow; static constexpr auto ps3GamepadMap = transformMappedKeys(genericGamepadMap, genericGamepadKeycodeToPS3HID); static constexpr auto ouyaGamepadMap = transformMappedKeys(genericGamepadMap, genericGamepadKeycodeToOuya); - static constexpr auto eightBitdoGamepadMap = transformMappedKeys(genericGamepadMap, genericGamepadKeycodeToBitdo); #endif #if defined(__ANDROID__) && __ARM_ARCH == 7 @@ -275,17 +261,15 @@ constexpr std::span genericKeyConfigs() KeyConfigDesc{Map::SYSTEM, DeviceSubtype::GENERIC_GAMEPAD, "Generic Gamepad", genericGamepadMap}, KeyConfigDesc{Map::SYSTEM, DeviceSubtype::PS3_CONTROLLER, "PS3 Controller", ps3GamepadMap}, KeyConfigDesc{Map::SYSTEM, DeviceSubtype::OUYA_CONTROLLER, "OUYA Controller", ouyaGamepadMap}, - KeyConfigDesc{Map::SYSTEM, DeviceSubtype::_8BITDO_SF30_PRO, "8Bitdo SF30 Pro", eightBitdoGamepadMap}, - KeyConfigDesc{Map::SYSTEM, DeviceSubtype::_8BITDO_SN30_PRO_PLUS, "8BitDo SN30 Pro+", eightBitdoGamepadMap}, #endif #if defined(__ANDROID__) && __ARM_ARCH == 7 - KeyConfigDesc{Map::SYSTEM, DeviceSubtype::XPERIA_PLAY, "Xperia Play", xperiaPlayGamepadMap}, + KeyConfigDesc{Map::SYSTEM, DeviceSubtype::XPERIA_PLAY, "Xperia Play", xperiaPlayGamepadMap}, #endif #ifdef CONFIG_MACHINE_PANDORA KeyConfigDesc{Map::SYSTEM, DeviceSubtype::PANDORA_HANDHELD, "Pandora Keys", pandoraKeysMap}, #endif #ifdef CONFIG_INPUT_APPLE_GAME_CONTROLLER - KeyConfigDesc{Map::APPLE_GAME_CONTROLLER,"Default", appleGamepadMap}, + KeyConfigDesc{Map::APPLE_GAME_CONTROLLER, "Default", appleGamepadMap}, #endif #ifdef CONFIG_INPUT_BLUETOOTH KeyConfigDesc{Map::WIIMOTE, "Default", wiimoteMap}, diff --git a/EmuFramework/src/EmuApp.cc b/EmuFramework/src/EmuApp.cc index f9b8cf8fb..749ebbf7d 100644 --- a/EmuFramework/src/EmuApp.cc +++ b/EmuFramework/src/EmuApp.cc @@ -527,7 +527,7 @@ void EmuApp::mainInitCommon(IG::ApplicationInitParams initParams, IG::Applicatio win.setAcceptDnd(true); renderer.setWindowValidOrientations(win, menuOrientation()); inputManager.updateInputDevices(ctx); - vController.configure(win, renderer, viewManager.defaultFace); + vController.configure(win, renderer, viewManager.defaultFace, viewManager.quadIndices); if(EmuSystem::inputHasKeyboard) { vController.setKeyboardImage(asset(AssetID::keyboardOverlay)); diff --git a/EmuFramework/src/RewindManager.cc b/EmuFramework/src/RewindManager.cc index 872e8df74..273915012 100644 --- a/EmuFramework/src/RewindManager.cc +++ b/EmuFramework/src/RewindManager.cc @@ -66,18 +66,20 @@ bool RewindManager::reset() void RewindManager::saveState(EmuApp &app) { - assert(maxStates); + assumeExpr(maxStates); + assumeExpr(stateIdx < maxStates); //log.debug("saving rewind state index:{}", stateIdx); auto &entry = stateEntries[stateIdx]; + stateIdx = stateIdx + 1 == maxStates ? 0 : stateIdx + 1; entry.size = app.writeState({entry.data, stateSize}, {.uncompressed = true}); - stateIdx = (stateIdx + 1) % maxStates; } void RewindManager::rewindState(EmuApp &app) { if(!maxStates) return; - auto prevIdx = (stateIdx - 1) % maxStates; + assumeExpr(stateIdx < maxStates); + auto prevIdx = stateIdx ? stateIdx - 1 : maxStates - 1; auto &entry = stateEntries[prevIdx]; if(!entry.size) return; diff --git a/EmuFramework/src/gui/ButtonConfigView.cc b/EmuFramework/src/gui/ButtonConfigView.cc index fe17802d3..31a5c744d 100644 --- a/EmuFramework/src/gui/ButtonConfigView.cc +++ b/EmuFramework/src/gui/ButtonConfigView.cc @@ -201,7 +201,7 @@ void ButtonConfigSetView::initPointerUI() void ButtonConfigSetView::place() { text.compile(renderer(), {.alignment = Gfx::TextAlignment::center}); - using Quad = decltype(quads)::Quad; + using Quad = decltype(quads)::Type; auto map = quads.map(); Quad{{.bounds = viewRect().as()}}.write(map, 0); if(pointerUIIsInit()) @@ -278,15 +278,24 @@ bool ButtonConfigSetView::inputEvent(const Input::Event &e) } return true; } + if(contains(pushedKeys, keyEv.mapKey())) + { + return true; + } + if((contains(pushedKeys, Input::Keycode::GAME_L2) || contains(pushedKeys, Input::Keycode::GAME_R2)) && + (keyEv.mapKey() == Input::Keycode::JS_LTRIGGER_AXIS || keyEv.mapKey() == Input::Keycode::JS_RTRIGGER_AXIS)) + { + log.info("ignoring trigger axis to avoid duplicate events since L2/R2 keys are pushed"); + return true; + } pushedKeys.tryPushBack(keyEv.mapKey()); - return true; } else if(keyEv.released()) { if(pushedKeys.size()) finalize(); } - return false; + return true; } }, e); } diff --git a/EmuFramework/src/gui/InputManagerView.cc b/EmuFramework/src/gui/InputManagerView.cc index a670301a7..d6ecb4a19 100644 --- a/EmuFramework/src/gui/InputManagerView.cc +++ b/EmuFramework/src/gui/InputManagerView.cc @@ -772,6 +772,7 @@ void InputManagerDeviceView::onShow() void InputManagerDeviceView::confirmICadeMode() { devConf.setICadeMode(iCadeMode.flipBoolValue(*this)); + devConf.save(inputManager); onShow(); app().defaultVController().setPhysicalControlsPresent(appContext().keyInputIsPresent()); } diff --git a/EmuFramework/src/gui/PlaceVControlsView.cc b/EmuFramework/src/gui/PlaceVControlsView.cc index cf6111fc4..7cf488bde 100644 --- a/EmuFramework/src/gui/PlaceVControlsView.cc +++ b/EmuFramework/src/gui/PlaceVControlsView.cc @@ -17,10 +17,12 @@ #include #include #include +#include namespace EmuEx { +constexpr SystemLogger log{"PlaceVControlsView"}; constexpr std::array snapPxSizes{0, 2, 4, 8, 16, 32, 64}; PlaceVControlsView::PlaceVControlsView(ViewAttachParams attach, VController &vController_): @@ -28,7 +30,8 @@ PlaceVControlsView::PlaceVControlsView(ViewAttachParams attach, VController &vCo exitText{"Exit", &defaultFace()}, snapText{"Snap: 0px", &defaultFace()}, vController{vController_}, - quads{attach.rendererTask, {.size = 4}} + quads{attach.rendererTask, {.size = 4}}, + gridIdxs{attach.rendererTask, 2, 2} { app().applyOSNavStyle(appContext(), true); } @@ -46,14 +49,37 @@ void PlaceVControlsView::place() exitBtnRect = WRect{{}, exitText.pixelSize()} + (viewRect().pos(C2DO) - exitText.pixelSize() / 2) + WPt{0, exitText.height()}; snapBtnRect = WRect{{}, snapText.pixelSize()} + (viewRect().pos(C2DO) - snapText.pixelSize() / 2) - WPt{0, exitText.height()}; const int lineSize = 1; - using Quad = decltype(quads)::Quad; + auto snapPxSize = snapPxSizes[snapPxIdx]; + size_t hLines = snapPxSize >= 16 ? std::max(viewRect().ySize() / snapPxSize, 1) : 1; + size_t vLines = snapPxSize >= 16 ? std::max(viewRect().xSize() / snapPxSize, 1) : 1; + quads.reset({.size = 2 + hLines + vLines}); + using Quad = decltype(quads)::Type; auto map = quads.map(); - Quad{{.bounds = WRect{{viewRect().x, viewRect().yCenter()}, - {viewRect().x2, viewRect().yCenter() + lineSize}}.as()}}.write(map, 0); - Quad{{.bounds = WRect{{viewRect().xCenter(), viewRect().y}, - {viewRect().xCenter() + lineSize, viewRect().y2}}.as()}}.write(map, 1); - Quad{{.bounds = exitBtnRect.as()}}.write(map, 2); - Quad{{.bounds = snapBtnRect.as()}}.write(map, 3); + Quad{{.bounds = exitBtnRect.as()}}.write(map, 0); + Quad{{.bounds = snapBtnRect.as()}}.write(map, 1); + if(hLines == 1 && vLines == 1) // center lines + { + Quad{{.bounds = WRect{{viewRect().x, viewRect().yCenter()}, + {viewRect().x2, viewRect().yCenter() + lineSize}}.as()}}.write(map, 2); + Quad{{.bounds = WRect{{viewRect().xCenter(), viewRect().y}, + {viewRect().xCenter() + lineSize, viewRect().y2}}.as()}}.write(map, 3); + } + else // full grid + { + for(auto i : iotaCount(hLines)) + { + int yPos = viewRect().y + ((i + 1) * snapPxSize); + Quad{{.bounds = WRect{{viewRect().x, yPos}, + {viewRect().x2, yPos + lineSize}}.as()}}.write(map, 2 + i); + } + for(auto i : iotaCount(vLines)) + { + int xPos = viewRect().x + ((i + 1) * snapPxSize); + Quad{{.bounds = WRect{{xPos, viewRect().y}, + {xPos + lineSize, viewRect().y2}}.as()}}.write(map, 2 + hLines + i); + } + } + gridIdxs.reset(hLines + vLines, 2); } bool PlaceVControlsView::inputEvent(const Input::Event &e) @@ -118,7 +144,7 @@ bool PlaceVControlsView::inputEvent(const Input::Event &e) auto bounds = window().bounds(); d.elem->setPos(newPos, bounds); auto layoutPos = VControllerLayoutPosition::fromPixelPos(d.elem->bounds().pos(C2DO), d.elem->bounds().size(), bounds); - //logMsg("set pos %d,%d from %d,%d", layoutPos.pos.x, layoutPos.pos.y, layoutPos.origin.xScaler(), layoutPos.origin.yScaler()); + //log.info("set pos {},{} from {},{}", layoutPos.pos.x, layoutPos.pos.y, layoutPos.origin.xScaler(), layoutPos.origin.yScaler()); auto &vCtrlLayoutPos = d.elem->layoutPos[layoutIdx]; vCtrlLayoutPos.origin = layoutPos.origin; vCtrlLayoutPos.pos = layoutPos.pos; @@ -138,7 +164,7 @@ bool PlaceVControlsView::inputEvent(const Input::Event &e) { snapPxIdx = (snapPxIdx + 1) % snapPxSizes.size(); snapText.resetString(std::format("Snap: {}px", snapPxSizes[snapPxIdx])); - snapText.compile(renderer()); + place(); postDraw(); } }); @@ -150,14 +176,14 @@ bool PlaceVControlsView::inputEvent(const Input::Event &e) void PlaceVControlsView::draw(Gfx::RendererCommands &__restrict__ cmds) { using namespace IG::Gfx; - vController.draw(cmds, true); cmds.setColor({.5, .5, .5}); auto &basicEffect = cmds.basicEffect(); basicEffect.disableTexture(cmds); - cmds.setVertexArray(quads); - cmds.drawQuads(quadIndices(), 0, 2); // centering lines + cmds.drawQuads(quads, gridIdxs, 0, quads.size() - 2); // grid + vController.draw(cmds, true); cmds.setColor({0, 0, 0, .5}); - cmds.drawQuads(quadIndices(), 2, 2); // button bg + basicEffect.disableTexture(cmds); + cmds.drawQuads(quads, quadIndices(), 0, 2); // button bg basicEffect.enableAlphaTexture(cmds); exitText.draw(cmds, exitBtnRect.pos(C2DO), C2DO, ColorName::WHITE); snapText.draw(cmds, snapBtnRect.pos(C2DO), C2DO, ColorName::WHITE); diff --git a/EmuFramework/src/gui/PlaceVControlsView.hh b/EmuFramework/src/gui/PlaceVControlsView.hh index e8e0ff6b9..3bdcfa058 100644 --- a/EmuFramework/src/gui/PlaceVControlsView.hh +++ b/EmuFramework/src/gui/PlaceVControlsView.hh @@ -53,6 +53,7 @@ private: Input::DragTracker dragTracker; size_t snapPxIdx{}; Gfx::IQuads quads; + Gfx::QuadIndexArray gridIdxs; }; } diff --git a/EmuFramework/src/gui/PlaceVideoView.cc b/EmuFramework/src/gui/PlaceVideoView.cc index b155aa6a7..50705d4cb 100644 --- a/EmuFramework/src/gui/PlaceVideoView.cc +++ b/EmuFramework/src/gui/PlaceVideoView.cc @@ -53,7 +53,7 @@ void PlaceVideoView::place() resetBounds = btnBounds; resetBounds.x = viewRect().xSize() / 2; const int lineSize = 1; - using Quad = decltype(quads)::Quad; + using Quad = decltype(quads)::Type; auto map = quads.map(); Quad{{.bounds = WRect{{viewRect().x, viewRect().yCenter()}, {viewRect().x2, viewRect().yCenter() + lineSize}}.as()}}.write(map, 0); diff --git a/EmuFramework/src/gui/TouchConfigView.cc b/EmuFramework/src/gui/TouchConfigView.cc index e85c62f49..bd151a4d2 100644 --- a/EmuFramework/src/gui/TouchConfigView.cc +++ b/EmuFramework/src/gui/TouchConfigView.cc @@ -50,12 +50,6 @@ constexpr int touchCtrlExtraBtnSizeMenuVal[4] 0, 10, 20, 30 }; -static void drawVControllerElement(Gfx::RendererCommands &__restrict__ cmds, const VControllerElement &elem, size_t layoutIdx) -{ - cmds.set(Gfx::BlendMode::PREMULT_ALPHA); - elem.draw(cmds, layoutIdx); -} - static void addCategories(EmuApp &app, VControllerElement &elem, auto &&addCategory) { if(elem.uiButtonGroup()) @@ -238,7 +232,7 @@ class DPadElementConfigView : public TableView, public EmuAppHelper &quadIdxs) { setWindow(win); setRenderer(renderer); setFace(face); + quadIdxsPtr = &quadIdxs; // common index buffer from view manager class + fanQuadIdxs = {renderer.mainTask, {.size = 24}}; // for rendering DPads with FanQuads + { + auto indices = fanQuadIdxs.map(); + for(auto i : iotaCount(2)) + { + std::ranges::copy(Gfx::mapFanQuadIndices(i), indices.begin() + (i * 12)); + } + } + gamepadTex = app().asset(AssetID::gamepadOverlay); + uiTex = app().asset(AssetID::more); for(auto &e : uiElements) { update(e); }; for(auto &e : gpElements) { @@ -1032,7 +1074,7 @@ void VController::updateSystemKeys(KeyInfo key, bool isPushed) if(stripFlags(btn.key) == key) { btn.isHighlighted = isPushed; - btn.setAlpha(alphaF); + btn.setAlpha(alphaF, grp); } } }, diff --git a/EmuFramework/src/vcontrols/VControllerButton.cc b/EmuFramework/src/vcontrols/VControllerButton.cc index b1963430c..57df3b034 100644 --- a/EmuFramework/src/vcontrols/VControllerButton.cc +++ b/EmuFramework/src/vcontrols/VControllerButton.cc @@ -27,8 +27,6 @@ void VControllerButton::setPos(WPt pos, WRect viewBounds, _2DOrigin o) bounds_.setPos(pos, o); bounds_.fitIn(viewBounds); extendedBounds_.setPos(bounds_.pos(C2DO), C2DO); - boundQuads.write(0, {.bounds = extendedBounds_.as()}); - updateSprite(); } void VControllerButton::setSize(WSize size, WSize extendedSize) @@ -36,16 +34,12 @@ void VControllerButton::setSize(WSize size, WSize extendedSize) size.y /= aspectRatio; bounds_ = makeWindowRectRel(bounds_.pos(C2DO), size); extendedBounds_ = bounds_ + WRect{{-extendedSize}, {extendedSize}}; - boundQuads.write(0, {.bounds = extendedBounds_.as()}); } -void VControllerButton::setImage(Gfx::RendererTask &task, Gfx::TextureSpan t, int aR) +void VControllerButton::setImage(Gfx::TextureSpan t, int aR) { - quad = {task, {.size = 1}}; texture = t; - boundQuads = {task, {.size = 1}}; aspectRatio = aR; - updateSprite(); } std::string VControllerButton::name(const EmuApp &app) const @@ -53,18 +47,6 @@ std::string VControllerButton::name(const EmuApp &app) const return std::string{app.inputManager.toString(key)}; } -void VControllerButton::drawBounds(Gfx::RendererCommands &__restrict__ cmds) const -{ - float brightness = isHighlighted ? 2.f : 1.f; - cmds.setColor(Gfx::Color{.5f}.multiplyRGB(brightness)); - cmds.drawQuad(boundQuads, 0); -} - -void VControllerButton::drawSprite(Gfx::RendererCommands &__restrict__ cmds) const -{ - cmds.basicEffect().drawSprite(cmds, quad, 0, texture); -} - void VControllerButton::setAlpha(float alpha) { float brightness = isHighlighted ? 2.f : 1.f; @@ -83,12 +65,6 @@ void VControllerButton::setAlpha(float alpha) else spriteColor = Gfx::Color{alpha}.multiplyRGB(brightness); } - updateSprite(); -} - -void VControllerButton::updateSprite() -{ - quad.write(0, {.bounds = bounds_.as(), .color = spriteColor, .textureSpan = texture}); } } diff --git a/EmuFramework/src/vcontrols/VControllerButtonGroup.cc b/EmuFramework/src/vcontrols/VControllerButtonGroup.cc index 0a70e51a6..fb1306981 100644 --- a/EmuFramework/src/vcontrols/VControllerButtonGroup.cc +++ b/EmuFramework/src/vcontrols/VControllerButtonGroup.cc @@ -105,6 +105,7 @@ void VControllerButtonGroup::setPos(WPt pos, WindowRect viewBounds) bounds_.fitIn(viewBounds); layoutButtons(buttons, bounds_, viewBounds, btnSize, spacingPixels, btnStagger, btnRowShift, layout.rowItems, LB2DO); + updateSprites(); } void VControllerButtonGroup::setButtonSize(int sizePx) @@ -120,6 +121,7 @@ void VControllerButtonGroup::setButtonSize(int sizePx) { b.setSize({sizePx, sizePx}, extendedSize); } + updateSprites(); } void VControllerButtonGroup::setStaggerType(uint8_t type) @@ -190,28 +192,12 @@ std::array VControllerButtonGroup::findButtonIndices(WPt windowPos) return btnOut; } -void VControllerButtonGroup::drawButtons(Gfx::RendererCommands &__restrict__ cmds) const -{ - cmds.basicEffect().enableTexture(cmds); - for(auto &b : buttons) - { - if(!b.enabled) - continue; - b.drawSprite(cmds); - } -} - void VControllerButtonGroup::drawBounds(Gfx::RendererCommands &__restrict__ cmds) const { - if(!layout.showBoundingArea) + if(!layout.showBoundingArea || !enabledBtns) return; cmds.basicEffect().disableTexture(cmds); - for(const auto &b : buttons) - { - if(!b.enabled) - continue; - b.drawBounds(cmds); - } + cmds.drawQuads(boundQuads, 0, enabledBtns); } static std::string namesString(auto &buttons, const EmuApp &app) @@ -287,6 +273,7 @@ void VControllerUIButtonGroup::setPos(WPt pos, WRect viewBounds) bounds_.fitIn(viewBounds); layoutButtons(buttons, bounds_, viewBounds, btnSize, 0, 0, 0, layout.rowItems, LT2DO); + updateSprites(); } void VControllerUIButtonGroup::setButtonSize(int sizePx) @@ -300,6 +287,7 @@ void VControllerUIButtonGroup::setButtonSize(int sizePx) { b.setSize({sizePx, sizePx}); } + updateSprites(); } int VControllerUIButtonGroup::rows() const @@ -308,27 +296,74 @@ int VControllerUIButtonGroup::rows() const return divRoundUp(buttonsToLayout(buttons), layout.rowItems); } -void VControllerUIButtonGroup::drawButtons(Gfx::RendererCommands &__restrict__ cmds) const +std::string VControllerUIButtonGroup::name(const EmuApp &app) const { - cmds.basicEffect().enableTexture(cmds); - for(auto &b : buttons) + return namesString(buttons, app); +} + +void VControllerUIButtonGroup::Config::validate(const EmuApp &app) +{ + for(auto &k : keys) { k = app.inputManager.validateSystemKey(k, true); } + if(!isValidRowItemCount(layout.rowItems)) + layout.rowItems = 2; +} + +template +void BaseVControllerButtonGroup::drawButtons(Gfx::RendererCommands &__restrict__ cmds) const +{ + auto &g = static_cast(*this); + if(!g.enabledBtns) + return; + cmds.drawQuads(g.quads, 0, g.enabledBtns); +} + +template +void BaseVControllerButtonGroup::updateSprites() +{ + auto &g = static_cast(*this); + if(!g.quads.hasTask()) + return; + g.quads.reset({.size = g.buttons.size()}); + Gfx::MappedBuffer boundQuadsMap; + if constexpr(requires {g.boundQuads;}) + { + g.boundQuads.reset({.size = g.buttons.size()}); + boundQuadsMap = g.boundQuads.map(); + } + auto quadsMap = g.quads.map(); + g.enabledBtns = 0; + for(auto &b : g.buttons) { if(!b.enabled) continue; - b.drawSprite(cmds); + b.spriteIdx = g.enabledBtns++; + if constexpr(requires {g.boundQuads;}) + b.updateSprite(quadsMap, boundQuadsMap); + else + b.updateSprite(quadsMap); } } -std::string VControllerUIButtonGroup::name(const EmuApp &app) const +template +void BaseVControllerButtonGroup::updateSprite(VControllerButton &b) { - return namesString(buttons, app); + auto &g = static_cast(*this); + assert(b.spriteIdx < g.quads.size()); + if constexpr(requires {g.boundQuads;}) + b.updateSprite(g.quads, g.boundQuads); + else + b.updateSprite(g.quads); } -void VControllerUIButtonGroup::Config::validate(const EmuApp &app) +template +void BaseVControllerButtonGroup::setAlpha(float alpha) { - for(auto &k : keys) { k = app.inputManager.validateSystemKey(k, true); } - if(!isValidRowItemCount(layout.rowItems)) - layout.rowItems = 2; + auto &g = static_cast(*this); + for(auto &b : g.buttons) { b.setAlpha(alpha); } + updateSprites(); } +template class BaseVControllerButtonGroup; +template class BaseVControllerButtonGroup; + } diff --git a/EmuFramework/src/vcontrols/VControllerDPad.cc b/EmuFramework/src/vcontrols/VControllerDPad.cc index ed20e3812..e1dda18e5 100644 --- a/EmuFramework/src/vcontrols/VControllerDPad.cc +++ b/EmuFramework/src/vcontrols/VControllerDPad.cc @@ -137,14 +137,17 @@ void VControllerDPad::transposeKeysForPlayer(const EmuApp &app, int player) void VControllerDPad::drawButtons(Gfx::RendererCommands &__restrict__ cmds) const { - cmds.basicEffect().drawSprite(cmds, spriteQuads, 0, tex); + cmds.setVertexArray(spriteQuads); + cmds.drawPrimitiveElements(Gfx::Primitive::TRIANGLE, 0, 12, Gfx::attribType); } void VControllerDPad::drawBounds(Gfx::RendererCommands &__restrict__ cmds) const { if(!config.visualizeBounds) return; - cmds.basicEffect().drawSprite(cmds, spriteQuads, 1, mapImg); + cmds.setVertexArray(spriteQuads); + cmds.basicEffect().enableTexture(cmds, mapImg); + cmds.drawPrimitiveElements(Gfx::Primitive::TRIANGLE, 12, 12, Gfx::attribType); } std::array VControllerDPad::getInput(WPt c) const @@ -192,17 +195,25 @@ void VControllerDPad::setAlpha(float a) void VControllerDPad::updateSprite() { - decltype(spriteQuads)::Quad spr{{.bounds = padBaseArea.as(), .textureSpan = tex}}; - decltype(spriteQuads)::Quad mapSpr{{.bounds = padArea.as(), .textureSpan = mapImg}}; - std::array colors; + decltype(spriteQuads)::Type spr{{.bounds = padBaseArea.as(), .textureSpan = tex}}; + decltype(spriteQuads)::Type mapSpr{{.bounds = padArea.as(), .textureSpan = mapImg}}; + std::array colors; colors.fill({alpha}); - if(isHighlighted[0] || isHighlighted[3]) + bool exclusiveLeftIsHighlighted = isHighlighted[3] && !isHighlighted[0] && !isHighlighted[2]; + bool exclusiveUpIsHighlighted = isHighlighted[0] && !isHighlighted[1] && !isHighlighted[3]; + bool exclusiveRightIsHighlighted = isHighlighted[1] && !isHighlighted[0] && !isHighlighted[2]; + bool exclusiveDownIsHighlighted = isHighlighted[2] && !isHighlighted[1] && !isHighlighted[3]; + if((isHighlighted[0] && isHighlighted[3]) // up-left + || exclusiveLeftIsHighlighted || exclusiveUpIsHighlighted) colors[0] = colors[0].multiplyRGB(2.f); - if(isHighlighted[2] || isHighlighted[1]) + if((isHighlighted[2] && isHighlighted[1]) // down-right + || exclusiveRightIsHighlighted || exclusiveDownIsHighlighted) colors[3] = colors[3].multiplyRGB(2.f); - if(isHighlighted[2] || isHighlighted[3]) + if((isHighlighted[2] && isHighlighted[3]) // bottom left + || exclusiveLeftIsHighlighted || exclusiveDownIsHighlighted) colors[1] = colors[1].multiplyRGB(2.f); - if(isHighlighted[0] || isHighlighted[1]) + if((isHighlighted[0] && isHighlighted[1]) // top right + || exclusiveRightIsHighlighted || exclusiveUpIsHighlighted) colors[2] = colors[2].multiplyRGB(2.f); for(auto &&[i, vtx] : enumerate(spr)) { vtx.color = colors[i]; } for(auto &&[i, vtx] : enumerate(mapSpr)) { vtx.color = colors[i]; } diff --git a/NES.emu/src/main/FceuApi.cc b/NES.emu/src/main/FceuApi.cc index c93e79dee..e6c89a976 100644 --- a/NES.emu/src/main/FceuApi.cc +++ b/NES.emu/src/main/FceuApi.cc @@ -219,7 +219,8 @@ int FCEUD_FDSReadBIOS(void *buff, uint32 size) const auto &fdsBiosPath = sys.fdsBiosPath; if(fdsBiosPath.empty()) { - throw std::runtime_error{"No FDS BIOS set"}; + sys.loaderErrorString = "No FDS BIOS set"; + return -1; } logMsg("loading FDS BIOS:%s", fdsBiosPath.data()); if(EmuApp::hasArchiveExtension(appCtx.fileUriDisplayName(fdsBiosPath))) @@ -236,19 +237,22 @@ int FCEUD_FDSReadBIOS(void *buff, uint32 size) auto io = entry.releaseIO(); if(io.size() != size) { - throw std::runtime_error{"Incompatible FDS BIOS"}; + sys.loaderErrorString = "Incompatible FDS BIOS"; + return -1; } return io.read(buff, size); } } - throw std::runtime_error{"Error opening FDS BIOS"}; + sys.loaderErrorString = "Error opening FDS BIOS"; + return -1; } else { auto io = appCtx.openFileUri(fdsBiosPath, IOAccessHint::All); if(io.size() != size) { - throw std::runtime_error{"Incompatible FDS BIOS"}; + sys.loaderErrorString = "Incompatible FDS BIOS"; + return -1; } return io.read(buff, size); } diff --git a/NES.emu/src/main/Main.cc b/NES.emu/src/main/Main.cc index a7ed48ba8..60cf2c6ec 100755 --- a/NES.emu/src/main/Main.cc +++ b/NES.emu/src/main/Main.cc @@ -375,7 +375,10 @@ void NesSystem::loadContent(IO &io, EmuSystemCreateParams, OnLoadProgressDelegat } if(!FCEUI_LoadGameWithFileVirtual(file, contentFileName().data(), 0, false)) { - throw std::runtime_error("Error loading game"); + if(loaderErrorString.size()) + throw std::runtime_error(std::exchange(loaderErrorString, {})); + else + throw std::runtime_error("Error loading game"); } autoDetectedRegion = regionFromName(contentFileName()); setRegion(optionVideoSystem.val, optionDefaultVideoSystem.val, autoDetectedRegion); diff --git a/NES.emu/src/main/MainSystem.hh b/NES.emu/src/main/MainSystem.hh index 100914a70..da260b68e 100644 --- a/NES.emu/src/main/MainSystem.hh +++ b/NES.emu/src/main/MainSystem.hh @@ -98,6 +98,7 @@ public: std::string palettesDir; std::string defaultPalettePath; std::string fdsBiosPath; + std::string loaderErrorString; bool fastForwardDuringFdsAccess = true; bool fdsIsAccessing{}; Byte1Option optionFourScore{CFGKEY_FOUR_SCORE, 0}; diff --git a/NES.emu/src/main/input.cc b/NES.emu/src/main/input.cc index f997534d4..19a530e9e 100644 --- a/NES.emu/src/main/input.cc +++ b/NES.emu/src/main/input.cc @@ -20,6 +20,7 @@ #include "MainSystem.hh" #include "MainApp.hh" #include +#include namespace EmuEx { @@ -36,6 +37,8 @@ enum class NesKey : KeyCode Start = 4, A = 1, B = 2, + + toggleDiskSide = 255, }; constexpr auto dpadKeyInfo = makeArray @@ -62,6 +65,11 @@ constexpr auto turboFaceKeyInfo = turbo(faceKeyInfo); constexpr std::array comboKeyInfo{KeyInfo{std::array{NesKey::A, NesKey::B}}}; +constexpr auto exKeyInfo = makeArray +( + NesKey::toggleDiskSide +); + constexpr auto gpKeyInfo = concatToArrayNow; constexpr auto gp2KeyInfo = transpose(gpKeyInfo, 1); constexpr auto gp3KeyInfo = transpose(gpKeyInfo, 2); @@ -75,6 +83,7 @@ std::span NesApp::keyCategories() KeyCategory{"Gamepad 2", gp2KeyInfo, 1}, KeyCategory{"Gamepad 3", gp2KeyInfo, 2}, KeyCategory{"Gamepad 4", gp2KeyInfo, 3}, + KeyCategory{"Extra Functions", exKeyInfo}, }; return categories; } @@ -91,6 +100,7 @@ std::string_view NesApp::systemKeyCodeToString(KeyCode c) case NesKey::Start: return "Start"; case NesKey::A: return "A"; case NesKey::B: return "B"; + case NesKey::toggleDiskSide: return "Eject Disk/Switch Side"; default: return ""; } } @@ -212,40 +222,74 @@ static unsigned playerInputShift(int player) return 0; } -void NesSystem::handleInputAction(EmuApp *, InputAction a) +void NesSystem::handleInputAction(EmuApp *app, InputAction a) { int player = a.flags.deviceId; auto key = NesKey(a.code); - auto gpBits = bit(a.code - 1); - if(GameInfo->type == GIT_NSF && a.isPushed()) + if(key == NesKey::toggleDiskSide) { - if(key == NesKey::Up) - FCEUI_NSFChange(10); - else if(key == NesKey::Down) - FCEUI_NSFChange(-10); - else if(key == NesKey::Right) - FCEUI_NSFChange(1); - else if(key == NesKey::Left) - FCEUI_NSFChange(-1); - else if(key == NesKey::B) - FCEUI_NSFChange(0); - } - else if(GameInfo->type == GIT_VSUNI) // TODO: make coin insert separate key - { - if(a.isPushed() && key == NesKey::Start) - FCEUI_VSUniCoin(); + if(!isFDS || !a.isPushed()) + return; + if(app) + app->syncEmulationThread(); + if(FCEU_FDSInserted()) + { + FCEU_FDSInsert(); + if(app) + app->postMessage("Disk ejected, push again to switch side"); + } + else + { + FCEU_FDSSelect(); + FCEU_FDSInsert(); + auto fdsSideToString = [](uint8_t side) + { + switch(side) + { + case 0: return "Disk 1 Side A"; + case 1: return "Disk 1 Side B"; + case 2: return "Disk 2 Side A"; + case 3: return "Disk 2 Side B"; + } + std::unreachable(); + }; + if(app) + app->postMessage(std::format("Set {}", fdsSideToString(FCEU_FDSCurrentSide()))); + } } - else if(GameInfo->inputfc == SIFC_HYPERSHOT) + else // gamepad bits { - if(auto hsKey = gpBits & 0x3; - hsKey) + auto gpBits = bit(a.code - 1); + if(GameInfo->type == GIT_NSF && a.isPushed()) + { + if(key == NesKey::Up) + FCEUI_NSFChange(10); + else if(key == NesKey::Down) + FCEUI_NSFChange(-10); + else if(key == NesKey::Right) + FCEUI_NSFChange(1); + else if(key == NesKey::Left) + FCEUI_NSFChange(-1); + else if(key == NesKey::B) + FCEUI_NSFChange(0); + } + else if(GameInfo->type == GIT_VSUNI) // TODO: make coin insert separate key + { + if(a.isPushed() && key == NesKey::Start) + FCEUI_VSUniCoin(); + } + else if(GameInfo->inputfc == SIFC_HYPERSHOT) { - hsKey = hsKey == 0x3 ? 0x3 : hsKey ^ 0x3; // swap the 2 bits - auto hsPlayerInputShift = player == 1 ? 3 : 1; - fcExtData = IG::setOrClearBits(fcExtData, hsKey << hsPlayerInputShift, a.isPushed()); + if(auto hsKey = gpBits & 0x3; + hsKey) + { + hsKey = hsKey == 0x3 ? 0x3 : hsKey ^ 0x3; // swap the 2 bits + auto hsPlayerInputShift = player == 1 ? 3 : 1; + fcExtData = IG::setOrClearBits(fcExtData, hsKey << hsPlayerInputShift, a.isPushed()); + } } + padData = setOrClearBits(padData, gpBits << playerInputShift(player), a.isPushed()); } - padData = setOrClearBits(padData, gpBits << playerInputShift(player), a.isPushed()); } bool NesSystem::onPointerInputStart(const Input::MotionEvent &e, Input::DragTrackerState, IG::WindowRect gameRect) diff --git a/imagine/include/imagine/gfx/BasicEffect.hh b/imagine/include/imagine/gfx/BasicEffect.hh index 3e8b9b6f9..7496243bf 100644 --- a/imagine/include/imagine/gfx/BasicEffect.hh +++ b/imagine/include/imagine/gfx/BasicEffect.hh @@ -66,6 +66,13 @@ public: enableTexture(cmds, texture); cmds.drawQuads(verts, idxs, startIdx, size); } + + template + void drawSprites(RendererCommands &cmds, const Buffer &verts, ssize_t startIdx, size_t size, auto &&texture) + { + enableTexture(cmds, texture); + cmds.drawQuads(verts, startIdx, size); + } }; } diff --git a/imagine/include/imagine/gfx/Buffer.hh b/imagine/include/imagine/gfx/Buffer.hh index 0fd8d8fb8..446b5c409 100644 --- a/imagine/include/imagine/gfx/Buffer.hh +++ b/imagine/include/imagine/gfx/Buffer.hh @@ -33,7 +33,8 @@ template class MappedBuffer { public: - constexpr MappedBuffer(ByteBuffer buff): + constexpr MappedBuffer() = default; + constexpr MappedBuffer(MappedByteBuffer buff): buff{std::move(buff)} {} constexpr operator std::span() const { return span(); } constexpr std::span span() const { return {data(), size()}; } @@ -48,7 +49,7 @@ public: constexpr auto end() const { return data() + size(); } protected: - ByteBuffer buff; + MappedByteBuffer buff; }; template @@ -63,11 +64,13 @@ public: Buffer(RendererTask &rTask, BufferConfig config): BaseBuffer(rTask, config.toByteConfig()) {} explicit operator bool() const { return BaseBuffer::operator bool(); } Renderer &renderer() const { return task().renderer(); } - RendererTask &task() const { return BaseBuffer::task(); } + RendererTask &task() const { return *BaseBuffer::taskPtr(); } + bool hasTask() const { return BaseBuffer::taskPtr(); } void setTask(RendererTask &task) { return BaseBuffer::setTask(task); } void reset(BufferConfig config) { BaseBuffer::reset(config.toByteConfig()); } size_t size() const { return BaseBuffer::sizeBytes() / elemSize; } - MappedBuffer map() { return {BaseBuffer::map()}; } + MappedBuffer map(ssize_t offset, size_t size) { return {BaseBuffer::map(offset * elemSize, size * elemSize)}; } + MappedBuffer map() { return map(0, 0); } }; template @@ -76,4 +79,39 @@ using VertexBuffer = Buffer; template using IndexBuffer = Buffer; +template +class ObjectBufferConfig +{ +public: + size_t size; + BufferUsageHint usageHint{BufferUsageHint::dynamic}; + + constexpr BufferConfig toBufferConfig() const + { + return + { + .size = size * T::vertexCount, + .usageHint = usageHint, + }; + } +}; + +template +class ObjectVertexBuffer : public VertexBuffer +{ +public: + using Type = T; + using Vertex = T::Vertex; + using BaseBuffer = VertexBuffer; + using BaseBuffer::map; + + constexpr ObjectVertexBuffer() = default; + ObjectVertexBuffer(RendererTask &rTask, ObjectBufferConfig config): VertexBuffer{rTask, config.toBufferConfig()} {} + void reset(ObjectBufferConfig config) { VertexBuffer::reset(config.toBufferConfig()); } + size_t size() const { return BaseBuffer::size() / T::vertexCount; } + MappedBuffer map(ssize_t offset, size_t size) { return BaseBuffer::map(offset * T::vertexCount, size * T::vertexCount); } + void write(ssize_t offset, T obj) { obj.write(*this, offset); } + void write(ssize_t offset, T::InitParams params) { write(offset, T{params}); } +}; + } diff --git a/imagine/include/imagine/gfx/FanQuads.hh b/imagine/include/imagine/gfx/FanQuads.hh new file mode 100644 index 000000000..81b244164 --- /dev/null +++ b/imagine/include/imagine/gfx/FanQuads.hh @@ -0,0 +1,120 @@ +/* This file is part of Imagine. + + Imagine is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Imagine is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Imagine. If not, see */ + +#include +#include + +namespace IG::Gfx +{ + +// A quad split into 4 triangles with a center vertex + +template +class BaseFanQuad : public BaseQuad +{ +public: + using Base = BaseQuad; + using Pos = Base::Pos; + using PosRect = Base::PosRect; + using PosPoint = Base::PosPoint; + using InitParams = Base::InitParams; + using TexCoordRect = Base::TexCoordRect; + using Base::v; + + static constexpr size_t vertexCount = 5; + V centerV; + + constexpr BaseFanQuad() = default; + + constexpr BaseFanQuad(InitParams params) + { + setPos(params.bounds); + if constexpr(requires {V::color;}) + { + for(auto &vtx : *this) { vtx.color = params.color; } + } + if constexpr(requires {V::texCoord;}) + { + if(params.textureSpan) + params.textureBounds = Base::remapTexCoordRect(params.textureSpan.bounds); + setUV(params.textureBounds, params.rotation); + } + } + + constexpr void setPos(QuadPoints p) + { + v = mapQuadPos(v, p.bl, p.tl, p.tr, p.br); + centerV.pos = {std::midpoint(p.bl.x, p.tr.x), std::midpoint(p.bl.y, p.tr.y)}; + } + + constexpr void setUV(TexCoordRect rect, Rotation r = Rotation::UP) + { + if constexpr(requires {V::texCoord;}) + { + v = mapQuadUV(v, rect, r); + centerV.texCoord = {std::midpoint(v[0].texCoord.x, v[3].texCoord.x), std::midpoint(v[0].texCoord.y, v[3].texCoord.y)}; + } + } + + constexpr std::array toArray() const + { + std::array arr; + std::ranges::copy(v, arr.begin()); + arr[4] = centerV; + return arr; + } + + void write(Buffer &buff, ssize_t offset) const + { + buff.task().write(buff, toArray(), offset * vertexCount); + } + + constexpr void write(std::span span, ssize_t offset) const + { + std::ranges::copy(toArray(), span.begin() + offset * vertexCount); + } + + constexpr auto size() const { return v.size() + 1; } + constexpr auto end() { return v.end() + 1; } + constexpr auto end() const { return v.end() + 1; } +}; + +using ILitTexFanQuad = BaseFanQuad; + +using ILitTexFanQuads = ObjectVertexBuffer; + +template +constexpr std::array mapFanQuadIndices(T baseIdx) +{ + baseIdx *= 5; + static constexpr T tl = 0, bl = 1, tr = 2, br = 3, cen = 4; + return + {{ + T(baseIdx + tl), + T(baseIdx + bl), + T(baseIdx + cen), + T(baseIdx + tl), + T(baseIdx + cen), + T(baseIdx + tr), + T(baseIdx + tr), + T(baseIdx + br), + T(baseIdx + cen), + T(baseIdx + br), + T(baseIdx + bl), + T(baseIdx + cen), + }}; +} + +} diff --git a/imagine/include/imagine/gfx/Quads.hh b/imagine/include/imagine/gfx/Quads.hh index 966787902..ee604e696 100644 --- a/imagine/include/imagine/gfx/Quads.hh +++ b/imagine/include/imagine/gfx/Quads.hh @@ -28,23 +28,6 @@ namespace IG::Gfx { -template -class QuadIndexArray : public IndexBuffer -{ -public: - constexpr QuadIndexArray() = default; - - QuadIndexArray(RendererTask &rTask, size_t maxQuads, BufferUsageHint usageHint = BufferUsageHint::constant): - IndexBuffer{rTask, {.size = maxQuads * 6, .usageHint = usageHint}} - { - auto indices = this->map(); - for(auto i : iotaCount(maxQuads)) - { - std::ranges::copy(makeRectIndexArray(i), indices.begin() + (i * 6)); - } - } -}; - template constexpr auto mapQuadUV(std::array v, Rectangle auto rect, Rotation r = Rotation::UP) { @@ -113,6 +96,22 @@ constexpr OutRect remapTexCoordRect(FRect rect) }; } +template +struct QuadPoints +{ + using Point = Point2D; + + Point bl, tl, tr, br; + + constexpr QuadPoints(Point bl, Point tl, Point tr, Point br): + bl{bl}, tl{tl}, tr{tr}, br{br} {} + + constexpr QuadPoints(Rect2 r): + bl{r.x, r.y}, tl{r.x, r.y2}, tr{r.x2, r.y2}, br{r.x2, r.y} {} + + constexpr QuadPoints(WRect r): QuadPoints{r.as()} {} +}; + template class BaseQuad { @@ -125,7 +124,7 @@ public: using TexCoord = IG_GetValueTypeOr(V::texCoord.x, UnusedType); using TexCoordRect = std::conditional_t, UnusedType>; - struct RectInitParams + struct InitParams { PosRect bounds{}; Color color{}; @@ -136,11 +135,12 @@ public: static constexpr int tlIdx = 0, blIdx = 1, trIdx = 2, brIdx = 3; - std::array v; + static constexpr size_t vertexCount = 4; + std::array v; constexpr BaseQuad() = default; - constexpr BaseQuad(RectInitParams params) + constexpr BaseQuad(InitParams params) { setPos(params.bounds); if constexpr(requires {V::color;}) @@ -155,27 +155,11 @@ public: } } - constexpr void setPos(PosPoint bl, PosPoint tl, PosPoint tr, PosPoint br) - { - v = mapQuadPos(v, bl, tl, tr, br); - } - - constexpr void setPos(const auto &quad) - { - setPos( - {quad.v[0].pos.x, quad.v[0].pos.y}, - {quad.v[1].pos.x, quad.v[1].pos.y}, - {quad.v[3].pos.x, quad.v[3].pos.y}, - {quad.v[2].pos.x, quad.v[2].pos.y}); - } - - constexpr void setPos(PosRect rect) + constexpr void setPos(QuadPoints p) { - setPos({rect.x, rect.y}, {rect.x, rect.y2}, {rect.x2, rect.y2}, {rect.x2, rect.y}); + v = mapQuadPos(v, p.bl, p.tl, p.tr, p.br); } - constexpr void setPos(WRect rect) { setPos(rect.as()); } - constexpr void setUV(TexCoordRect rect, Rotation r = Rotation::UP) { if constexpr(requires {V::texCoord;}) @@ -202,12 +186,12 @@ public: void write(Buffer &buff, ssize_t offset) const { - buff.task().write(buff, v, offset * 4); + buff.task().write(buff, v, offset * vertexCount); } constexpr void write(std::span span, ssize_t offset) const { - std::ranges::copy(v, span.begin() + offset * 4); + std::ranges::copy(v, span.begin() + offset * vertexCount); } constexpr auto &operator[](size_t idx) { return v[idx]; } @@ -215,7 +199,6 @@ public: constexpr auto &tl() { return v[tlIdx]; } constexpr auto &tr() { return v[trIdx]; } constexpr auto &br() { return v[brIdx]; } - constexpr operator std::array&() { return v; } constexpr auto data() const { return v.data(); } constexpr auto size() const { return v.size(); } constexpr auto begin() { return v.begin(); } @@ -234,44 +217,69 @@ using IColQuad = BaseQuad; using IColTexQuad = BaseQuad; using ILitTexQuad = BaseQuad; +using Quads = ObjectVertexBuffer; +using TexQuads = ObjectVertexBuffer; +using ColQuads = ObjectVertexBuffer; +using ColTexQuads = ObjectVertexBuffer; +using IQuads = ObjectVertexBuffer; +using ITexQuads = ObjectVertexBuffer; +using IColQuads = ObjectVertexBuffer; +using IColTexQuads = ObjectVertexBuffer; +using ILitTexQuads = ObjectVertexBuffer; + template -class QuadsConfig +constexpr std::array mapQuadIndices(T baseIdx) +{ + baseIdx *= 4; + static constexpr T tl = 0, bl = 1, tr = 2, br = 3; + return + {{ + T(baseIdx + tl), + T(baseIdx + bl), + T(baseIdx + br), + T(baseIdx + tl), + T(baseIdx + br), + T(baseIdx + tr), + }}; +} + +template +class QuadIndexArray : public IndexBuffer { public: - size_t size; - BufferUsageHint usageHint{BufferUsageHint::dynamic}; + using BaseBuffer = IndexBuffer; - constexpr BufferConfig toBufferConfig() const + constexpr QuadIndexArray() = default; + + QuadIndexArray(RendererTask &rTask, size_t maxQuads, size_t startOffset = 0, BufferUsageHint usageHint = BufferUsageHint::constant): + IndexBuffer{rTask, {.size = maxQuads * 6, .usageHint = usageHint}} { - return - { - .size = size * 4, - .usageHint = usageHint, - }; + writeIndices(maxQuads, startOffset); } -}; -template -class BaseQuads : public VertexBuffer -{ -public: - using Quad = BaseQuad; + void reset(size_t maxQuads, size_t startOffset = 0) + { + BaseBuffer::reset({.size = maxQuads * 6}); + writeIndices(maxQuads, startOffset); + } - constexpr BaseQuads() = default; - BaseQuads(RendererTask &rTask, QuadsConfig config): VertexBuffer{rTask, config.toBufferConfig()} {} - void reset(QuadsConfig config) { VertexBuffer::reset(config.toBufferConfig()); } - void write(ssize_t offset, Quad quad) { quad.write(*this, offset); } - void write(ssize_t offset, Quad::RectInitParams params) { write(offset, Quad{params}); } -}; + void reserve(size_t maxQuads, size_t startOffset = 0) + { + if(maxQuads > size()) + reset(maxQuads, startOffset); + } + + size_t size() const { return BaseBuffer::size() / 6; } -using Quads = BaseQuads; -using TexQuads = BaseQuads; -using ColQuads = BaseQuads; -using ColTexQuads = BaseQuads; -using IQuads = BaseQuads; -using ITexQuads = BaseQuads; -using IColQuads = BaseQuads; -using IColTexQuads = BaseQuads; -using ILitTexQuads = BaseQuads; +private: + void writeIndices(size_t maxQuads, size_t startOffset) + { + auto indices = this->map(); + for(auto i : iotaCount(maxQuads)) + { + std::ranges::copy(mapQuadIndices(startOffset + i), indices.begin() + (i * 6)); + } + } +}; } diff --git a/imagine/include/imagine/gfx/RendererCommands.hh b/imagine/include/imagine/gfx/RendererCommands.hh index abab50b92..76b91d9c7 100644 --- a/imagine/include/imagine/gfx/RendererCommands.hh +++ b/imagine/include/imagine/gfx/RendererCommands.hh @@ -150,7 +150,7 @@ public: void drawPrimitiveElements(const Buffer &idxs, Primitive mode, int start, int count) { setIndexArray(idxs); - drawPrimitiveElements(mode, start, count, attribType); + drawPrimitiveElements(mode, start * sizeof(I), count, attribType); } template @@ -184,6 +184,13 @@ public: setVertexArray(verts); drawQuads(idxs, startIdx, size); } + + template + void drawQuads(const Buffer &verts, ssize_t startIdx, size_t size) + { + setVertexArray(verts); + drawPrimitiveElements(Primitive::TRIANGLE, startIdx * 6 * sizeof(I), size * 6, attribType); + } }; } diff --git a/imagine/include/imagine/gfx/RendererTask.hh b/imagine/include/imagine/gfx/RendererTask.hh index 801aeb66d..11e61868a 100644 --- a/imagine/include/imagine/gfx/RendererTask.hh +++ b/imagine/include/imagine/gfx/RendererTask.hh @@ -86,8 +86,8 @@ public: } else { - auto map = buff.map(); - std::ranges::copy(data, map.begin() + offset); + auto map = buff.map(offset, std::size(data)); + std::ranges::copy(data, map.begin()); } } }; diff --git a/imagine/include/imagine/gfx/defs.hh b/imagine/include/imagine/gfx/defs.hh index e5337e6be..bab7d4967 100644 --- a/imagine/include/imagine/gfx/defs.hh +++ b/imagine/include/imagine/gfx/defs.hh @@ -234,20 +234,6 @@ struct GlyphSetMetrics int16_t yLineStart{}; }; -constexpr std::array makeRectIndexArray(uint8_t baseIdx) -{ - baseIdx *= 4; - return - {{ - baseIdx, - uint8_t(baseIdx+1), - uint8_t(baseIdx+3), - baseIdx, - uint8_t(baseIdx+3), - uint8_t(baseIdx+2), - }}; -} - enum class BufferType : uint8_t { vertex, diff --git a/imagine/include/imagine/gfx/opengl/GLBuffer.hh b/imagine/include/imagine/gfx/opengl/GLBuffer.hh index 6c17b7600..4df814371 100644 --- a/imagine/include/imagine/gfx/opengl/GLBuffer.hh +++ b/imagine/include/imagine/gfx/opengl/GLBuffer.hh @@ -39,6 +39,8 @@ struct GLBufferRefDeleter }; using UniqueGLBufferRef = UniqueResource; +using MappedByteBuffer = ByteBufferS; + template class GLBuffer { @@ -46,12 +48,13 @@ public: constexpr GLBuffer() = default; GLBuffer(RendererTask &, ByteBufferConfig); explicit operator bool() const { return bool(buffer); } - RendererTask &task() const { return *buffer.get_deleter().rTaskPtr; } + RendererTask *taskPtr() const { return buffer.get_deleter().rTaskPtr; } + RendererTask &task() const { return *taskPtr(); } void setTask(RendererTask &task) { buffer.get_deleter().rTaskPtr = &task; } GLBufferRef name() const { return buffer.get(); } void reset(ByteBufferConfig); size_t sizeBytes() const { return sizeBytes_; } - ByteBuffer map(); + MappedByteBuffer map(ssize_t offset, size_t size); void writeSubData(ssize_t offset, size_t size, const void *data); static void writeSubData(GLuint name, ssize_t offset, size_t size, const void *data); diff --git a/imagine/include/imagine/input/inputDefs.hh b/imagine/include/imagine/input/inputDefs.hh index c1481f8b1..38d17a7ae 100644 --- a/imagine/include/imagine/input/inputDefs.hh +++ b/imagine/include/imagine/input/inputDefs.hh @@ -68,15 +68,10 @@ enum class DeviceSubtype : uint8_t NONE = 0, XPERIA_PLAY = 1, PS3_CONTROLLER = 2, - MOTO_DROID_KEYBOARD = 3, OUYA_CONTROLLER = 4, PANDORA_HANDHELD = 5, - XBOX_360_CONTROLLER = 6, GENERIC_GAMEPAD = 8, APPLE_EXTENDED_GAMEPAD = 9, - _8BITDO_SF30_PRO = 10, - _8BITDO_SN30_PRO_PLUS = 11, - _8BITDO_M30_GAMEPAD = 12 }; enum class Action : uint8_t diff --git a/imagine/include/imagine/util/memory/Buffer.hh b/imagine/include/imagine/util/memory/Buffer.hh index 55e0948c0..88c7a28dd 100644 --- a/imagine/include/imagine/util/memory/Buffer.hh +++ b/imagine/include/imagine/util/memory/Buffer.hh @@ -26,10 +26,10 @@ namespace IG { -template +template struct BufferDeleter { - using DeleterFunc = DelegateFuncS; + using DeleterFunc = DelegateFuncS; DeleterFunc del{}; size_t size{}; @@ -45,11 +45,11 @@ struct BufferDeleter // Wrapper around unique_ptr with custom deleter & a size, used to pass buffers allocated // with different APIs (new, mmap, Android Assets, etc.) using the same interface -template +template class Buffer { public: - using Deleter = BufferDeleter>; + using Deleter = BufferDeleter, deleterSize>; using DeleterFunc = typename Deleter::DeleterFunc; friend Buffer>; @@ -118,4 +118,10 @@ protected: using ByteBuffer = Buffer; using ConstByteBuffer = Buffer; +template +using ByteBufferS = Buffer; + +template +using ConstByteBufferS = Buffer; + } diff --git a/imagine/src/base/android/input.cc b/imagine/src/base/android/input.cc index 69a4e53db..5c3b6eb53 100644 --- a/imagine/src/base/android/input.cc +++ b/imagine/src/base/android/input.cc @@ -88,21 +88,6 @@ static std::pair mapKeycodesForSpecialDevices(const Inpu } break; } - case Device::Subtype::XBOX_360_CONTROLLER: - { - if(keyCode) - break; - // map d-pad on wireless controller adapter - auto scanCode = AKeyEvent_getScanCode(event); - switch(scanCode) - { - case 704: return {Keycode::LEFT, src}; - case 705: return {Keycode::RIGHT, src}; - case 706: return {Keycode::UP, src}; - case 707: return {Keycode::DOWN, src}; - } - break; - } default: switch(keyCode) // map volume/media keys to keyboard source { diff --git a/imagine/src/base/android/inputConfig.cc b/imagine/src/base/android/inputConfig.cc index 74056d6c3..091232d3b 100644 --- a/imagine/src/base/android/inputConfig.cc +++ b/imagine/src/base/android/inputConfig.cc @@ -164,9 +164,11 @@ AndroidInputDevice::AndroidInputDevice(JNIEnv* env, jobject aDev, // check trigger axes static constexpr AxisId triggerAxes[]{AxisId::LTRIGGER, AxisId::RTRIGGER, AxisId::GAS, AxisId::BRAKE}; static constexpr AxisFlags triggerAxesBits[]{{.lTrigger = true}, {.rTrigger = true}, {.gas = true}, {.brake = true}}; + AxisFlags addedTriggers{}; for(auto &axisId : triggerAxes) { - bool hasAxis = asInt(jsAxisFlags & triggerAxesBits[std::distance(triggerAxes, &axisId)]); + auto triggerAxisBits = triggerAxesBits[std::distance(triggerAxes, &axisId)]; + bool hasAxis = asInt(jsAxisFlags & triggerAxisBits); if(!hasAxis) { continue; @@ -174,6 +176,9 @@ AndroidInputDevice::AndroidInputDevice(JNIEnv* env, jobject aDev, logMsg("trigger axis:%d", (int)axisId); axis.emplace_back(*this, axisId); assert(!axis.isFull()); + addedTriggers |= triggerAxisBits; + if(addedTriggers == AxisFlags{.lTrigger = true, .rTrigger = true}) + break; // don't parse Gas/Brake if triggers are present to avoid duplicate events } } } @@ -429,7 +434,6 @@ void AndroidApplication::initInput(ApplicationContext ctx, JNIEnv *env, jobject else if(buildDevice == "sholes") { logMsg("detected Droid/Milestone keyboard"); - genericKeyDev.setSubtype(Input::Device::Subtype::MOTO_DROID_KEYBOARD); } } builtinKeyboardDev = addAndroidInputDevice(ctx, std::move(genericKeyDev), false); diff --git a/imagine/src/gfx/opengl/Buffer.cc b/imagine/src/gfx/opengl/Buffer.cc index 6f9279a7e..1d0ff815c 100644 --- a/imagine/src/gfx/opengl/Buffer.cc +++ b/imagine/src/gfx/opengl/Buffer.cc @@ -18,6 +18,10 @@ #include "internalDefs.hh" #include +#ifndef GL_MAP_INVALIDATE_RANGE_BIT +#define GL_MAP_INVALIDATE_RANGE_BIT 0x0004 +#endif + #ifndef GL_MAP_INVALIDATE_BUFFER_BIT #define GL_MAP_INVALIDATE_BUFFER_BIT 0x0008 #endif @@ -98,20 +102,23 @@ void GLBuffer::reset(ByteBufferConfig config) } template -ByteBuffer GLBuffer::map() +MappedByteBuffer GLBuffer::map(ssize_t offset, size_t size) { + assert(offset + size <= sizeBytes()); + if(!size) + size = sizeBytes(); if(hasBufferMap(task().renderer())) { void *ptr; - task().run([this, &ptr]() + task().run([this, &ptr, offset, size]() { auto target = toGLEnum(type); glBindBuffer(target, name()); ptr = task().renderer().support.glMapBufferRange(target, - 0, sizeBytes(), GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_WRITE_BIT); - //log.debug("mapped buffer:0x{:X} to {}", name(), ptr); + offset, size, GL_MAP_WRITE_BIT); + //log.debug("mapped offset:{} size:{} of buffer:0x{:X} to {}", offset, size, name(), ptr); }, true); - return {{static_cast(ptr), sizeBytes()}, [this](const uint8_t *ptr, size_t) + return {{static_cast(ptr), size}, [this](const uint8_t *ptr, size_t) { task().run([name = name(), &support = task().renderer().support]() { @@ -124,12 +131,12 @@ ByteBuffer GLBuffer::map() } else { - return {{std::make_unique(sizeBytes()).release(), sizeBytes()}, [this](const uint8_t *ptr, size_t) + return {{std::make_unique(size).release(), size}, [this, offset](const uint8_t *ptr, size_t size) { - task().run([name = name(), sizeBytes = sizeBytes(), dataPtr = ptr]() + task().run([name = name(), offset = offset, sizeBytes = size, dataPtr = ptr]() { //log.debug("writing mapped data to buffer:0x{:X}", name); - writeSubData(name, 0, sizeBytes, dataPtr); + writeSubData(name, offset, sizeBytes, dataPtr); delete[] dataPtr; }); }}; diff --git a/imagine/src/gui/TableView.cc b/imagine/src/gui/TableView.cc index d8449777e..43ee23887 100755 --- a/imagine/src/gui/TableView.cc +++ b/imagine/src/gui/TableView.cc @@ -115,7 +115,7 @@ void TableView::draw(Gfx::RendererCommands &__restrict__ cmds) color = headingColor; } auto rect = makeWindowRectRel({x, y-1}, {viewRect().xSize(), ySize}).as(); - vRect.emplace_back(IColQuad::RectInitParams{.bounds = rect, .color = color}); + vRect.emplace_back(IColQuad::InitParams{.bounds = rect, .color = color}); } y += yCellSize; if(vRect.size() == vRect.capacity()) [[unlikely]] diff --git a/imagine/src/gui/ViewStack.cc b/imagine/src/gui/ViewStack.cc index 58fef2c4f..14b671102 100644 --- a/imagine/src/gui/ViewStack.cc +++ b/imagine/src/gui/ViewStack.cc @@ -149,7 +149,7 @@ void ViewStack::place() top().place(); if(customDisplayRect.y2 > customViewRect.y2) // add a basic gradient in the OS navigation bar area { - decltype(bottomGradientQuads)::Quad bottomGradient; + decltype(bottomGradientQuads)::Type bottomGradient; bottomGradient.setPos(View::displayInsetRect(View::Direction::BOTTOM, customViewRect, customDisplayRect)); bottomGradient.bl().color = bottomGradient.br().color = Gfx::PackedColor::format.build(0., 0., 0., 1.); bottomGradient.tl().color = bottomGradient.tr().color = Gfx::PackedColor::format.build(0., 0., 0., 0.); diff --git a/imagine/src/input/Device.cc b/imagine/src/input/Device.cc index f2a507fe8..73fdc0ea6 100644 --- a/imagine/src/input/Device.cc +++ b/imagine/src/input/Device.cc @@ -426,31 +426,6 @@ static DeviceSubtype gamepadSubtype(std::string_view name) logMsg("detected OUYA gamepad"); return Device::Subtype::OUYA_CONTROLLER; } - else if(name.contains("NVIDIA Controller")) - { - logMsg("detected NVidia Shield gamepad"); - return {}; - } - else if(name == "Xbox 360 Wireless Receiver") - { - logMsg("detected wireless 360 gamepad"); - return Device::Subtype::XBOX_360_CONTROLLER; - } - else if(name == "8Bitdo SF30 Pro") - { - logMsg("detected 8Bitdo SF30 Pro"); - return Device::Subtype::_8BITDO_SF30_PRO; - } - else if(name == "8BitDo SN30 Pro+") - { - logMsg("detected 8BitDo SN30 Pro+"); - return Device::Subtype::_8BITDO_SN30_PRO_PLUS; - } - else if(name == "8BitDo M30 gamepad") - { - logMsg("detected 8BitDo M30 gamepad"); - return Device::Subtype::_8BITDO_M30_GAMEPAD; - } return {}; }