Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions include/data_structures/Context.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef CONTEXT_HPP
#define CONTEXT_HPP

#include <string>
#include <vector>

struct Context {
double timestamp;
std::vector<std::string>& markers;
};

#endif // CONTEXT_HPP
11 changes: 11 additions & 0 deletions include/data_structures/EEGData.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#ifndef EEGDATA_HPP
#define EEGDATA_HPP

#include <vector>

struct EEGData {
double timestamp;
std::vector<double> channelValues;
};

#endif // EEGDATA_HPP
11 changes: 11 additions & 0 deletions include/data_structures/Marker.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#ifndef MARKER_HPP
#define MARKER_HPP

#include <string>

struct Marker {
std::string name;
double timestamp;
};

#endif // MARKER_HPP
33 changes: 33 additions & 0 deletions include/renderer/Renderer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#ifndef RENDERER_HPP
#define RENDERER_HPP

#include <SDL2/SDL.h>
#include <concurrentqueue.h>

#include <memory>
#include <thread>

class Scene;
struct Marker;

class Renderer {
public:
Renderer() = delete;
Renderer(const std::shared_ptr<Scene>& scene, std::shared_ptr<SDL_Renderer> sdlRenderer,
std::shared_ptr<moodycamel::ConcurrentQueue<Marker>> markerQueue);
~Renderer() = default;

void render(const std::stop_token& stoken);

private:
struct SDLWindowDeleter {
void operator()(SDL_Window* window) const;
};
std::unique_ptr<SDL_Window, SDLWindowDeleter> window;
std::shared_ptr<SDL_Renderer> sdlRenderer;
std::weak_ptr<Scene> currentScene;
std::shared_ptr<moodycamel::ConcurrentQueue<Marker>> markerQueue;
std::jthread renderThread;
};

#endif // RENDERER_HPP
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ add_library(runtime_core OBJECT
scene/components/BlinkComponent.cpp
scene/SceneObject.cpp
scene/Scene.cpp
renderer/Renderer.cpp
${PROTO_SRCS}
${PROTO_HDRS}
)
Expand Down
58 changes: 58 additions & 0 deletions src/renderer/Renderer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#include "renderer/Renderer.hpp"

#include "data_structures/Context.hpp"
#include "data_structures/Marker.hpp"
#include "lsl_cpp.h"
#include "scene/Scene.hpp"

void Renderer::SDLWindowDeleter::operator()(SDL_Window* window) const {
if (window) {
SDL_DestroyWindow(window);
}
}

Renderer::Renderer(const std::shared_ptr<Scene>& scene, std::shared_ptr<SDL_Renderer> sdlRenderer,
std::shared_ptr<moodycamel::ConcurrentQueue<Marker>> markerQueue)
: window(nullptr, SDLWindowDeleter{}),
sdlRenderer(std::move(sdlRenderer)),
currentScene(scene),
markerQueue(std::move(markerQueue)) {}

void Renderer::render(const std::stop_token& stoken) {
std::vector<std::string> currentFrameMarkers;
auto lastTime = std::chrono::high_resolution_clock::now();

while (!stoken.stop_requested()) {
auto currentTime = std::chrono::high_resolution_clock::now();
double deltaTime = std::chrono::duration<double>(currentTime - lastTime).count();
lastTime = currentTime;

currentFrameMarkers.clear();
Context ctx{deltaTime, currentFrameMarkers};

SDL_Event event;
while (SDL_PollEvent(&event) == 1) {
if (event.type == SDL_QUIT) {
return;
}
}

if (auto scene = currentScene.lock()) {
scene->update(ctx);
}

SDL_RenderClear(sdlRenderer.get());

if (auto scene = currentScene.lock()) {
scene->render(sdlRenderer.get());
}

SDL_RenderPresent(sdlRenderer.get());

double exactTime = lsl::local_clock();

for (const auto& markerName : currentFrameMarkers) {
markerQueue->enqueue(Marker{markerName, exactTime});
}
}
}
141 changes: 141 additions & 0 deletions tests/unit_tests/RendererTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#include <gtest/gtest.h>

#include <atomic>
#include <memory>
#include <stop_token>

#include "data_structures/Context.hpp"
#include "data_structures/Marker.hpp"
#include "renderer/Renderer.hpp"
#include "scene/Scene.hpp"
#include "scene/SceneObject.hpp"
#include "scene/components/Component.hpp"

namespace {
constexpr int kDummySurfaceWidth = 10;
constexpr int kDummySurfaceHeight = 10;
constexpr int kDummySurfaceDepth = 32;
constexpr uint32_t kDummySurfaceFlags = 0;

class CustomComponent : public Component {
public:
CustomComponent(std::shared_ptr<SceneObject> owner, std::shared_ptr<std::atomic<int>> updates,
std::shared_ptr<std::atomic<int>> renders,
std::shared_ptr<std::stop_source> stopSource)
: Component(std::move(owner)),
updates(std::move(updates)),
renders(std::move(renders)),
stopSource(std::move(stopSource)) {}

void update(const Context& context) override {
(void)context;
(*updates)++;

stopSource->request_stop();
}

void render(SDL_Renderer* renderer) override {
(void)renderer;
(*renders)++;
}

private:
std::shared_ptr<std::atomic<int>> updates;
std::shared_ptr<std::atomic<int>> renders;
std::shared_ptr<std::stop_source> stopSource;
};

class MarkerComponent : public Component {
public:
MarkerComponent(std::shared_ptr<SceneObject> owner,
std::shared_ptr<std::stop_source> stopSource)
: Component(std::move(owner)), stopSource(std::move(stopSource)) {}

void update(const Context& context) override {
context.markers.push_back("test_marker");
stopSource->request_stop();
}

void render(SDL_Renderer* renderer) override { (void)renderer; }

private:
std::shared_ptr<std::stop_source> stopSource;
};

} // namespace

TEST(RendererTest, RenderLoop_WhenComponentAdded_CallsUpdateExactlyOnceBeforeStop) {
ASSERT_EQ(SDL_Init(SDL_INIT_EVENTS), 0);

auto scene = std::make_shared<Scene>();
auto obj = std::make_shared<SceneObject>("obj");

auto updates = std::make_shared<std::atomic<int>>(0);
auto renders = std::make_shared<std::atomic<int>>(0);
auto stop_source = std::make_shared<std::stop_source>();

obj->addComponent(std::make_unique<CustomComponent>(obj, updates, renders, stop_source));
scene->addObject(obj);

SDL_Surface* surface =
SDL_CreateRGBSurfaceWithFormat(kDummySurfaceFlags, kDummySurfaceWidth, kDummySurfaceHeight,
kDummySurfaceDepth, SDL_PIXELFORMAT_RGBA32);
SDL_Renderer* sdlRenderer = SDL_CreateSoftwareRenderer(surface);
auto sharedRenderer = std::shared_ptr<SDL_Renderer>(sdlRenderer, [surface](SDL_Renderer* r) {
if (r) {
SDL_DestroyRenderer(r);
}
if (surface) {
SDL_FreeSurface(surface);
}
});

auto markerQueue = std::make_shared<moodycamel::ConcurrentQueue<Marker>>();

Renderer renderer(scene, sharedRenderer, markerQueue);
renderer.render(stop_source->get_token());

EXPECT_EQ(*updates, 1);
EXPECT_EQ(*renders, 1);

SDL_Quit();
}

TEST(RendererTest, RenderLoop_QueuesMarkersFromComponents) {
ASSERT_EQ(SDL_Init(SDL_INIT_EVENTS), 0);

auto scene = std::make_shared<Scene>();
auto obj = std::make_shared<SceneObject>("obj");
auto stop_source = std::make_shared<std::stop_source>();

obj->addComponent(std::make_unique<MarkerComponent>(obj, stop_source));
scene->addObject(obj);

SDL_Surface* surface =
SDL_CreateRGBSurfaceWithFormat(kDummySurfaceFlags, kDummySurfaceWidth, kDummySurfaceHeight,
kDummySurfaceDepth, SDL_PIXELFORMAT_RGBA32);
SDL_Renderer* sdlRenderer = SDL_CreateSoftwareRenderer(surface);
auto sharedRenderer = std::shared_ptr<SDL_Renderer>(sdlRenderer, [surface](SDL_Renderer* r) {
if (r) {
SDL_DestroyRenderer(r);
}
if (surface) {
SDL_FreeSurface(surface);
}
});

auto markerQueue = std::make_shared<moodycamel::ConcurrentQueue<Marker>>();

Renderer renderer(scene, sharedRenderer, markerQueue);
renderer.render(stop_source->get_token());

Marker m;
bool dequeued = markerQueue->try_dequeue(m);
EXPECT_TRUE(dequeued);
if (dequeued) {
EXPECT_EQ(m.name, "test_marker");
EXPECT_FALSE(markerQueue->try_dequeue(m));
}

SDL_Quit();
}
Loading