Изучение современных шаблонов C ++, и это в итоге стало тем направлением, в котором я пошел, чтобы создать инкапсуляцию управления окнами SDL, пытаясь при этом придерживаться практики RAII. Это должно быть основой для программного рендерера (и, возможно, после этого он будет расширен до OpenGL).
окно / input.h
#pragma once
#include <map>
// State of a keyboard key
enum class KeyboardKeyState {
Up,
Down,
Pressed, // Key was previously down last check and just changed to the up state
};
// Keyboard keys that we are listening for events for
enum class KeyboardKey {
W, S, A, D, LeftArrow, RightArrow, UpArrow, DownArrow
};
struct InputState {
std::map<KeyboardKey, KeyboardKeyState> Keys{
{KeyboardKey::W, KeyboardKeyState::Up},
{KeyboardKey::S, KeyboardKeyState::Up},
{KeyboardKey::A, KeyboardKeyState::Up},
{KeyboardKey::D, KeyboardKeyState::Up},
{KeyboardKey::LeftArrow, KeyboardKeyState::Up},
{KeyboardKey::UpArrow, KeyboardKeyState::Up},
{KeyboardKey::DownArrow, KeyboardKeyState::Up},
{KeyboardKey::RightArrow, KeyboardKeyState::Up},
};
bool LeftMouseClicked{};
bool RightMouseClicked{};
int MouseDragX{};
int MouseDragY{};
int MouseScrollAmount{};
};
окно / window_state.h
#pragma once
struct WindowState {
bool quitRequested{false};
};
окно / window_settings.h
#pragma once
#include <optional>
#include <string>
#include <SDL.h>
// How the window will be rendered to (e.g. manually via a pixel buffer, OGL context, Vulkan context, etc...)
enum class WindowRenderMode {
Unspecified, // No render mode selected. Will throw exceptions
ByPixelBuffer, // Rendered using software rendering directly to a window's pixel buffer
};
// Contains options for the window display
struct WindowSettings {
// How many pixels wide the window should be. If unspecified it will match the display's width.
std::optional<int> Width;
// How many pixels tall the window should be. If unspecified it will match the display's height
std::optional<int> Height;
// If the window should be borderless or not
bool Borderless{false};
// The title to give the window
std::string Title{};
// How we intend to render to the window
WindowRenderMode RenderMode{WindowRenderMode::Unspecified};
};
окно / SDL / sdl_raii.h
#pragma once
// C++ can't deal with classes that use forward declared types, so we need all these RAII types
// in it's own header to keep it out of sdl_app_window.h
#include <memory>
#include <vector>
#include <SDL.h>
#include "window/window_settings.h"
namespace sdl_raii {
class SdlWindow {
public:
std::unique_ptr<SDL_Window, void(*)(SDL_Window*)> pointer;
explicit SdlWindow(const WindowSettings& windowSettings);
SdlWindow(const SdlWindow& other) = delete;
SdlWindow(SdlWindow&& other) = default;
};
class SdlRenderer {
public:
std::unique_ptr<SDL_Renderer, void(*)(SDL_Renderer*)> pointer;
explicit SdlRenderer(const SdlWindow& window);
SdlRenderer(const SdlRenderer& other) = delete;
SdlRenderer(SdlRenderer&& other) = default;
};
class SdlFullWindowTexture {
public:
std::unique_ptr<SDL_Texture, void(*)(SDL_Texture*)> pointer;
SdlFullWindowTexture(const WindowSettings& windowSettings, const SdlRenderer& renderer);
SdlFullWindowTexture(const SdlFullWindowTexture& other) = delete;
SdlFullWindowTexture(SdlFullWindowTexture&& other) = default;
};
}
окно / SDL / sdl_raii.cpp
#include <stdexcept>
#include "sdl_raii.h"
using namespace sdl_raii;
std::unique_ptr<SDL_Window, void (*)(SDL_Window*)> CreateSdlWindowPointer(const WindowSettings &windowSettings) {
if (windowSettings.RenderMode != WindowRenderMode::ByPixelBuffer) {
std::string error = "Unsupported render mode: ";
error += std::to_string((int) windowSettings.RenderMode);
throw std::runtime_error{error};
}
if (SDL_Init(SDL_INIT_EVERYTHING) != 0) {
std::string error{"Error initializing SDL: "};
error += SDL_GetError();
throw std::runtime_error{error};
}
unsigned int flags = windowSettings.Borderless ? SDL_WINDOW_BORDERLESS : 0;
auto sdlWin = SDL_CreateWindow(windowSettings.Title.c_str(),
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
windowSettings.Width.value(),
windowSettings.Height.value(),
flags);
if (sdlWin == nullptr) {
std::string error = "Error creating SDL window: ";
error += SDL_GetError();
throw std::runtime_error{error};
}
return std::unique_ptr<SDL_Window, void(*)(SDL_Window*)>(sdlWin, &SDL_DestroyWindow);
}
std::unique_ptr<SDL_Renderer, void (*)(SDL_Renderer*)> CreateSdlRendererPointer(const SdlWindow& sdlWindow) {
auto renderer = SDL_CreateRenderer(sdlWindow.pointer.get(), -1, 0);
if (!renderer) {
std::string error{"Error creating renderer: "};
error += SDL_GetError();
throw std::runtime_error{error};
}
return std::unique_ptr<SDL_Renderer, void (*)(SDL_Renderer*)>(renderer, &SDL_DestroyRenderer);
}
std::unique_ptr<SDL_Texture, void (*)(SDL_Texture*)>
CreateSdlTexturePointer(const WindowSettings &windowSettings, const SdlRenderer& sdlRenderer) {
auto pointer = SDL_CreateTexture(sdlRenderer.pointer.get(),
SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING,
windowSettings.Width.value(),
windowSettings.Height.value());
return std::unique_ptr<SDL_Texture, void (*)(SDL_Texture*)>(pointer, &SDL_DestroyTexture);
}
SdlWindow::SdlWindow(const WindowSettings &windowSettings)
: pointer{CreateSdlWindowPointer(windowSettings)} {
}
SdlRenderer::SdlRenderer(const SdlWindow& window)
: pointer{CreateSdlRendererPointer(window)} {
}
SdlFullWindowTexture::SdlFullWindowTexture(const WindowSettings &windowSettings, const SdlRenderer &renderer)
: pointer{CreateSdlTexturePointer(windowSettings, renderer)}{
}
окно / SDL / sdl_app_window.h
#pragma once
#include <SDL.h>
#include <memory>
#include <vector>
#include <span>
#include "window/input.h"
#include "window/window_state.h"
#include "window/window_settings.h"
#include "sdl_raii.h"
// A window that's managed by SDL
class SdlAppWindow {
public:
explicit SdlAppWindow(WindowSettings settings);
SdlAppWindow(const SdlAppWindow& other) = delete;
SdlAppWindow(SdlAppWindow&& other) = default;
// Retrieves a mutable view of the raw full screen pixel buffer.
std::span<unsigned int> GetPixelBuffer();
// Performs any work that needs to be done at the beginning of a frame.
void BeginFrame();
// Called after all render operations have occurred, in order to push the renderings to the window
void PresentFrame();
void HandleWindowEvents(WindowState& windowState, InputState& inputState);
private:
const WindowSettings windowSettings;
sdl_raii::SdlWindow sdlWindow;
sdl_raii::SdlRenderer sdlRenderer;
sdl_raii::SdlFullWindowTexture sdlFullWindowTexture;
// Full screen render buffer for use in the ByPixelBuffer render mode
std::vector<unsigned int> pixelBuffer;
static WindowSettings GetUpdatedWindowSettings(WindowSettings windowSettings);
std::vector<unsigned int> CreatePixelBuffer();
};
окно / SDL / sdl_app_window.cpp
#include <utility>
#include <stdexcept>
#include "sdl_app_window.h"
using std::vector;
SdlAppWindow::SdlAppWindow(WindowSettings settings) :
windowSettings{GetUpdatedWindowSettings(std::move(settings))},
sdlWindow{windowSettings},
sdlRenderer{sdlWindow},
sdlFullWindowTexture(windowSettings, sdlRenderer),
pixelBuffer{CreatePixelBuffer()} {
}
void SdlAppWindow::BeginFrame() {
switch (windowSettings.RenderMode) {
case WindowRenderMode::ByPixelBuffer:
// fill to black
std::fill(pixelBuffer.begin(), pixelBuffer.end(), 0xFF000000);
break;
default:
std::string error{"No begin frame support for render mode: "};
error += std::to_string((int) windowSettings.RenderMode);
throw std::runtime_error{error};
}
}
void SdlAppWindow::PresentFrame() {
int pitch = windowSettings.Width.value() * (int) sizeof (int);
SDL_UpdateTexture(sdlFullWindowTexture.pointer.get(),
nullptr,
pixelBuffer.data(),
pitch);
SDL_RenderCopy(sdlRenderer.pointer.get(), sdlFullWindowTexture.pointer.get(), nullptr, nullptr);
SDL_RenderPresent(sdlRenderer.pointer.get());
}
WindowSettings SdlAppWindow::GetUpdatedWindowSettings(WindowSettings windowSettings) {
SDL_DisplayMode displayMode;
SDL_GetCurrentDisplayMode(0, &displayMode);
if (!windowSettings.Width.has_value()) {
windowSettings.Width = displayMode.w;
}
if (!windowSettings.Height.has_value()) {
windowSettings.Height = displayMode.h;
}
return windowSettings;
}
std::vector<unsigned int> SdlAppWindow::CreatePixelBuffer() {
std::vector<unsigned int> result(windowSettings.Width.value() * windowSettings.Height.value());
return result;
}
std::span<unsigned int> SdlAppWindow::GetPixelBuffer() {
return std::span<unsigned int>{pixelBuffer};
}
void SdlAppWindow::HandleWindowEvents(WindowState& windowState, InputState &inputState) {
SDL_Event sdlEvent;
while (SDL_PollEvent(&sdlEvent))
{
switch (sdlEvent.type) {
case SDL_QUIT:
windowState.quitRequested = true;
break;
// todo: add case statements for keyboard/mouse processing
}
}
}
main.cpp
#include <iostream>
#include <SDL.h>
#include "window/window_settings.h"
#include "window/sdl/sdl_app_window.h"
int main(int argc, char* argv[]) {
constexpr unsigned int targetFps = 30;
constexpr unsigned int targetFrameTime = 1000 / targetFps;
WindowSettings settings{
1024,
768,
false,
std::string{"Test Window"},
WindowRenderMode::ByPixelBuffer,
};
SdlAppWindow appWindow{settings};
InputState inputState;
WindowState windowState;
unsigned int previousFrameTime = 0;
bool isRunning = true;
while(isRunning) {
unsigned int timeSinceLastFrame = SDL_GetTicks() - previousFrameTime;
int timeToWait = targetFrameTime - timeSinceLastFrame;
if (timeToWait > 0 && timeToWait <= targetFrameTime) {
SDL_Delay(timeToWait);
}
previousFrameTime = SDL_GetTicks();
appWindow.HandleWindowEvents(windowState, inputState);
appWindow.BeginFrame();
auto buffer = appWindow.GetPixelBuffer();
std::fill(buffer.begin(), buffer.end(), 0xFFFF0000);
appWindow.PresentFrame();
isRunning = !windowState.quitRequested;
}
return 0;
}
```
1 ответ
Обратите внимание: это всего лишь частичный обзор вашего кода.
Используйте пространства имен и вложенные классы
То, как вы называете вещи в своем коде, немного непоследовательно. Есть пространство имен sdl_raii
который содержит такие классы, как SdlWindow
, но есть еще SdlAppWindow
которого нет в пространстве имен, а также существуют такие классы, как WindowState
которые не находятся в пространстве имен и не имеют Sdl
префикс.
Я бы рекомендовал поместить все в пространство имен SDL
, и классы вложенности, где это необходимо. Например, поскольку WindowSettings
похоже, держит конфигурацию, специфичную для SdlWindow
, следующая иерархия имеет больше смысла:
namespace SDL {
class Window {
public:
class Settings {
...
};
Window(const Settings &settings);
...
}
};
Тогда код приложения будет выглядеть так:
int main(...) {
SDL::Window::Settings settings{
1024,
768,
false,
"Test Window",
SDL::Window::RenderMode::ByPixelBuffer,
};
SDL::AppWindow appWindow{settings};
...
}
Это также позволяет приложению использовать using namespace SDL
или же using SDL::Window
например, чтобы при желании уменьшить объем набора текста.
Переместите код из Create...Pointer()
в конструкторы
Конструкторы классов в sdl_raii
не делай ничего, кроме звонка Create...Pointer()
, а последние функции больше ничем не вызываются, поэтому такое разделение мне кажется совершенно ненужным. Я бы просто переместил весь код из этих Create
функции в конструкторы соответствующих классов.
Причина, по которой я использовал
sdl_raii
пространство имен для 3 классов, но неSdlAppWindow
это потому чтоSdlAppWindow
зависит от приложения, в то время как другие очень специфичны для целей RAII. Поэтому я переместил 3 класса ресурсов в их собственное пространство имен, поэтому, когда вы имеете в видуSdlAppWindow
у вас нет возможности для других (поскольку 0% вероятность, что вам нужны классы raii. Я не мог заставить вложенность работать должным образом из-за проблем с порядком (я не мог получить форвардные объявления с вложенными классами для правильной работы, и все это выглядело беспорядочно)— KallDrexx
Конструкторы Re: я сделал это так, иначе вы получите двойное распределение. Может, в этом нет ничего страшного?
— KallDrexx
Если вы хотите абстрагироваться от обработки окна нижнего уровня, тогда конечно, но кто-то может действительно захотеть построить
SdlWindow
прямо еслиSdlAppWindow
не предоставляет то, что им нужно. Что касается конструкторов: здесь нет двойных выделений.— Г. Сон