private void UpdateControllerAxis(GUITickEventArgs tickEventArgs, int joystickId, byte axisRaw, short axisValue)
        {
            var axis = (SDL.SDL_GameControllerAxis)axisRaw;

            switch (axis)
            {
            case SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY:
                var isUp   = axisValue < -20000;
                var isDown = axisValue > 20000;
                tickEventArgs.ButtonState.P1Up   = isUp;
                tickEventArgs.ButtonState.P2Up   = isUp;
                tickEventArgs.ButtonState.P1Down = isDown;
                tickEventArgs.ButtonState.P2Down = isDown;
                break;

            case SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX:
                var isRight = axisValue > 20000;
                var isLeft  = axisValue < -20000;
                tickEventArgs.ButtonState.P1Right = isRight;
                tickEventArgs.ButtonState.P2Right = isRight;
                tickEventArgs.ButtonState.P1Left  = isLeft;
                tickEventArgs.ButtonState.P2Left  = isLeft;
                break;
            }
        }
Example #2
0
        /**
         * Fired when the GUI event loop "ticks". This provides an opportunity
         * to receive user input as well as send the framebuffer to be rendered.
         */
        private static void Platform_OnTick(GUITickEventArgs eventArgs)
        {
            // Receive user input.
            _game.ButtonState.SetState(eventArgs.ButtonState);

            // If the PAUSE key was pressed (e.g. CTRL/CMD+BREAK), invoke the
            // interactive debugger.
            if (_game.Debug && eventArgs.ShouldBreak)
            {
                _game.Break();
            }

            if (eventArgs.ShouldPause)
            {
                _game.Pause();
            }

            if (eventArgs.ShouldUnPause)
            {
                _game.UnPause();
            }

            if (_renderFrameNextTick)
            {
                eventArgs.FrameBuffer  = _frameBuffer;
                eventArgs.ShouldRender = true;
                _renderFrameNextTick   = false;
            }
        }
        private void UpdateControllerButtons(GUITickEventArgs tickEventArgs, int joystickId, byte buttonRaw, bool isDown)
        {
            var button = (SDL.SDL_GameControllerButton)buttonRaw;

            switch (button)
            {
            // Player 1 & 2
            case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT:
                tickEventArgs.ButtonState.P1Left = isDown;
                tickEventArgs.ButtonState.P2Left = isDown;
                break;

            case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
                tickEventArgs.ButtonState.P1Right = isDown;
                tickEventArgs.ButtonState.P2Right = isDown;
                break;

            case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP:
                tickEventArgs.ButtonState.P1Up = isDown;
                tickEventArgs.ButtonState.P2Up = isDown;
                break;

            case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN:
                tickEventArgs.ButtonState.P1Down = isDown;
                tickEventArgs.ButtonState.P2Down = isDown;
                break;

            // Player 1/2 Start, Coin
            case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A:
                tickEventArgs.ButtonState.P1Start = isDown;
                break;

            case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X:
                tickEventArgs.ButtonState.P2Start = isDown;
                break;

            case SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y:
                tickEventArgs.ButtonState.CoinChute1 = isDown;
                break;
            }
        }
        private void UpdateKeys(GUITickEventArgs tickEventArgs, SDL.SDL_Keycode keycode, bool isDown)
        {
            switch (keycode)
            {
            // Player 1
            case SDL.SDL_Keycode.SDLK_LEFT:
                tickEventArgs.ButtonState.P1Left = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_RIGHT:
                tickEventArgs.ButtonState.P1Right = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_UP:
                tickEventArgs.ButtonState.P1Up = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_DOWN:
                tickEventArgs.ButtonState.P1Down = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_1:
                tickEventArgs.ButtonState.P1Start = isDown;
                break;

            // Player 2
            case SDL.SDL_Keycode.SDLK_a:
                tickEventArgs.ButtonState.P2Left = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_d:
                tickEventArgs.ButtonState.P2Right = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_w:
                tickEventArgs.ButtonState.P2Up = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_s:
                tickEventArgs.ButtonState.P2Down = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_2:
                tickEventArgs.ButtonState.P2Start = isDown;
                break;

            // Coins
            case SDL.SDL_Keycode.SDLK_5:
                tickEventArgs.ButtonState.CoinChute1 = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_6:
                tickEventArgs.ButtonState.CoinChute2 = isDown;
                break;

            // Servicing
            case SDL.SDL_Keycode.SDLK_3:
                tickEventArgs.ButtonState.ServiceCredit = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_7:
                tickEventArgs.ButtonState.ServiceRackAdvance = isDown;
                break;

            case SDL.SDL_Keycode.SDLK_8:
            {
                // The board test switch is generally a toggle switch because it must remain closed
                // for the game program to remain in the test mode. Here we have some extra logic
                // to make it so you don't have to hold down the button to stay in test mode. This
                // makes it work like a toggle switch.

                // If the key is pressed and we are allowing it to change, then flip its state.
                if (isDown && _allowChangeBoardTestSwitch)
                {
                    _boardTestSwitchActive = !_boardTestSwitchActive;

                    // Once we've flipped the state, don't allow changing it again until the key
                    // is released and pressed again.
                    _allowChangeBoardTestSwitch = false;
                }

                // Once the key is released, allow the pressing of the key to toggle the flag again
                // next time the key is pressed.
                if (!isDown)
                {
                    _allowChangeBoardTestSwitch = true;
                }

                break;
            }
            }
        }
        /**
         * Used to start the GUI event loop using the SDL window to poll for events. When an event is
         * received, the keyboard state is read and the OnTick event is fired. After the event completes
         * a frame can be rendered based on the values set in the OnTick event args.
         *
         * This is a BLOCKING call; the event loop will continue to be polled until (1) the user uses
         * the platform specific key combination or GUI action to close the window or (2) the OnTick
         * event arguments has ShouldQuit set to true.
         */
        public void StartLoop()
        {
            // Used to keep track of the time elapsed in each loop iteration. This is used to
            // notify the OnTick handlers so they can update their simulation, as well as throttle
            // the update loop to targetTicskHz if needed.
            var stopwatch = new Stopwatch();

            // For calculating how long the actual rendering of pixels take.
            // var renderStopwatch = new Stopwatch();

            // Structure used to pass data to and from the OnTick handlers. We initialize it once
            // outside of the loop to avoid eating a ton of memory putting GC into a tailspin.
            var tickEventArgs = new GUITickEventArgs();

            // The SDL event polled for in each iteration of the loop.
            SDL.SDL_Event sdlEvent;

            while (true)
            {
                stopwatch.Restart();

                tickEventArgs.KeyDown       = null;
                tickEventArgs.ShouldBreak   = false;
                tickEventArgs.ShouldUnPause = false;
                tickEventArgs.ShouldPause   = false;

                while (SDL.SDL_PollEvent(out sdlEvent) != 0)
                {
                    switch (sdlEvent.type)
                    {
                    // e.g. Command + Q, ALT+F4, or clicking X...
                    case SDL.SDL_EventType.SDL_QUIT:
                        // Break out of the SDL event loop, which will close the program.
                        return;

                    case SDL.SDL_EventType.SDL_WINDOWEVENT:
                    {
                        if (_isWinRT)
                        {
                            // On Xbox, most games keep running when focus is lost (e.g. the user pressed the home button
                            // to show the overlay, but hasn't left the app yet). So instead of pausing then, we'll wait
                            // until the app is hidden. This isn't great for UWP on Windows desktops, but the UWP app is
                            // intended only for Xbox... Windows desktop users should be using the desktop app version.

                            if (sdlEvent.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SHOWN)
                            {
                                tickEventArgs.ShouldUnPause = true;
                            }

                            if (sdlEvent.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_HIDDEN)
                            {
                                tickEventArgs.ShouldPause = true;
                            }
                        }
                        else
                        {
                            if (sdlEvent.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_GAINED)
                            {
                                tickEventArgs.ShouldUnPause = true;
                            }

                            if (sdlEvent.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_LOST)
                            {
                                tickEventArgs.ShouldPause = true;
                            }
                        }

                        if (sdlEvent.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_CLOSE)
                        {
                            return;     // Break out of the SDL event loop, which will close the program.
                        }
                        break;
                    }

                    case SDL.SDL_EventType.SDL_KEYDOWN:
                        tickEventArgs.KeyDown = sdlEvent.key.keysym.sym;
                        UpdateKeys(tickEventArgs, sdlEvent.key.keysym.sym, true);

                        // If the break/pause or F3 key is pressed, set a flag indicating the
                        // emulator's should activate the interactive debugger.
                        if (sdlEvent.key.keysym.sym == SDL.SDL_Keycode.SDLK_PAUSE ||
                            sdlEvent.key.keysym.sym == SDL.SDL_Keycode.SDLK_F3)
                        {
                            tickEventArgs.ShouldBreak = true;
                        }

                        break;

                    case SDL.SDL_EventType.SDL_KEYUP:
                        UpdateKeys(tickEventArgs, sdlEvent.key.keysym.sym, false);
                        break;

                    case SDL.SDL_EventType.SDL_CONTROLLERBUTTONDOWN:
                        UpdateControllerButtons(tickEventArgs, sdlEvent.cbutton.which, sdlEvent.cbutton.button, true);
                        break;

                    case SDL.SDL_EventType.SDL_CONTROLLERBUTTONUP:
                        UpdateControllerButtons(tickEventArgs, sdlEvent.cbutton.which, sdlEvent.cbutton.button, false);
                        break;

                    case SDL.SDL_EventType.SDL_CONTROLLERAXISMOTION:
                        UpdateControllerAxis(tickEventArgs, sdlEvent.caxis.which, sdlEvent.caxis.axis, sdlEvent.caxis.axisValue);
                        break;
                    }

                    if (_debugRendererSurface != IntPtr.Zero)
                    {
                        HandleDebuggerEvent(sdlEvent);
                    }
                }

                // Update the state of the board test toggle switch.
                tickEventArgs.ButtonState.ServiceBoardTest = _boardTestSwitchActive;

                // Update the event arguments that will be sent with the event handler.
                tickEventArgs.ShouldRender = false;

                // Delegate out to the event handler so work can be done.
                if (OnTick != null)
                {
                    OnTick(tickEventArgs);
                }

                // We only want to re-render if the frame buffer has changed since last time because
                // the SDL_RenderPresent method is relatively expensive.
                if (tickEventArgs.ShouldRender && tickEventArgs.FrameBuffer != null)
                {
                    // renderStopwatch.Restart();
                    // Console.WriteLine("Starting Render!");

                    // Clear the background with black so that if the game is letterboxed or pillarboxed
                    // the background color of the unused space will be black.
                    SDL.SDL_SetRenderDrawColor(_gameRendererSurface, 0, 0, 0, 255);
                    SDL.SDL_RenderClear(_gameRendererSurface);

                    // In order to pass the managed memory bitmap to the unmanaged SDL methods, we need to
                    // manually allocate memory for the byte array. This effectively "pins" it so it won't
                    // be garbage collected. We need to be sure to release this memory after we render.
                    var frameBuffer        = GCHandle.Alloc(tickEventArgs.FrameBuffer, GCHandleType.Pinned);
                    var frameBufferPointer = frameBuffer.AddrOfPinnedObject();

                    // Now that we have an unmanaged pointer to the bitmap, we can use the SDL methods to
                    // get an abstract stream interface which will allow us to load the bitmap as a texture.
                    var rwops   = SDL.SDL_RWFromConstMem(frameBufferPointer, tickEventArgs.FrameBuffer.Length);
                    var surface = SDL.INTERNAL_SDL_LoadBMP_RW(rwops, 0);
                    var texture = SDL.SDL_CreateTextureFromSurface(_gameRendererSurface, surface);

                    // Now that we've loaded the framebuffer's bitmap as a texture, we can now render it to
                    // the SDL canvas at the given location.
                    SDL.SDL_RenderCopy(_gameRendererSurface, texture, ref _renderLocation, ref _renderLocation);

                    // Ensure we release our unmanaged memory.
                    SDL.SDL_DestroyTexture(texture);
                    SDL.SDL_FreeSurface(surface);
                    SDL.SDL_FreeRW(rwops);
                    frameBuffer.Free();

                    // Now that we're done rendering, we can present this new frame.
                    SDL.SDL_RenderPresent(_gameRendererSurface);

                    // renderStopwatch.Stop();
                    // Console.WriteLine("Render completed in: " + renderStopwatch.ElapsedMilliseconds + " ms");
                }

                // Re-render the debugger window; we only do this if it needs re-rendering because part of the
                // rendering can be expensive as it examines CPU state including registers and memory locations
                // in order to obtain the data needed to be shown in the window (no need to do this @ 60hz).
                if (_debugRendererSurface != IntPtr.Zero)
                {
                    lock (_debuggerRenderingLock)
                    {
                        if (_debuggerNeedsRendering)
                        {
                            DebugWindowRenderer.Render(_debugRendererSurface, _debuggerState, _debuggerInputString, _debuggerFileList, _debuggerPcb, _debuggerShowAnnotatedDisassembly);
                            _debuggerNeedsRendering = false;
                        }
                    }
                }

                // See if we need to delay to keep locked to ~ targetTicskHz.

                if (stopwatch.Elapsed.TotalMilliseconds < (1000 / _targetTicksHz))
                {
                    var delay = (1000 / _targetTicksHz) - stopwatch.Elapsed.TotalMilliseconds;
                    SDL.SDL_Delay((uint)delay);
                }

                // If the event handler indicated we should quit, then stop.
                if (tickEventArgs.ShouldQuit)
                {
                    return;
                }
            }
        }