// // direct_renderer_test.cxx // graphics-test // // Created by Sam Jaffe on 6/1/19. // Copyright © 2019 Sam Jaffe. All rights reserved. // #include "game/graphics/renderer.hpp" #include #include #include #include #include "../src/renderer_impl.hpp" #include "game/graphics/exception.h" #include "game/graphics/object.hpp" #include "game/graphics/vertex.h" using testing::_; using testing::AnyNumber; using testing::IsEmpty; using testing::SizeIs; struct mock_renderer_impl : graphics::renderer_impl { MOCK_CONST_METHOD0(manager, std::shared_ptr()); MOCK_METHOD3(draw, void(identity, math::matr4 const &, std::vector const &)); MOCK_METHOD0(clear, void()); MOCK_METHOD0(flush, void()); }; identity cast(unsigned int id) { return *reinterpret_cast *>(&id); } struct DirectRendererTest : testing::Test { void SetUp() override; void TearDown() override; std::unique_ptr renderer; mock_renderer_impl * mock; }; void DirectRendererTest::SetUp() { mock = new mock_renderer_impl; renderer.reset(new graphics::direct_renderer(mock)); } void DirectRendererTest::TearDown() { renderer.reset(); delete mock; } graphics::object DemoObject(unsigned int id = 1) { math::dim2::rectangle const size{{{-1.f, -1.f}}, {{2.f, 2.f}}}; math::dim2::rectangle const tex{{{0.f, 0.f}}, {{1.f, 1.f}}}; return {size, size, cast(id), tex}; } TEST_F(DirectRendererTest, DrawPassesToDraw) { EXPECT_CALL(*mock, draw(cast(1), math::matr4(), IsEmpty())).Times(1); renderer->draw(cast(1), math::matr4(), {}); } TEST_F(DirectRendererTest, DrawObjectHasIdenTranslation) { auto identity = math::matrix::identity(); EXPECT_CALL(*mock, draw(cast(1), identity, _)).Times(1); renderer->draw(DemoObject()); } TEST_F(DirectRendererTest, DrawObjectHasSixVertices) { EXPECT_CALL(*mock, draw(_, _, SizeIs(6))).Times(1); renderer->draw(DemoObject()); } TEST_F(DirectRendererTest, MultipleDrawCallsDispatchMultipleDraws) { EXPECT_CALL(*mock, draw(_, _, SizeIs(6))).Times(2); renderer->draw(DemoObject()); renderer->draw(DemoObject()); } TEST_F(DirectRendererTest, ClearPassesToClear) { EXPECT_CALL(*mock, clear()).Times(1); renderer->clear(); } TEST_F(DirectRendererTest, FlushPassesToFlush) { EXPECT_CALL(*mock, flush()).Times(1); renderer->flush(); } struct BatchRendererTest : testing::Test { void SetUp() override; void TearDown() override; std::unique_ptr renderer; std::unique_ptr drenderer; mock_renderer_impl * mock; }; void BatchRendererTest::SetUp() { mock = new mock_renderer_impl; drenderer.reset(new graphics::direct_renderer(mock)); renderer.reset(new graphics::batch_renderer(drenderer.get())); } void BatchRendererTest::TearDown() { renderer.reset(); drenderer.reset(); delete mock; } TEST_F(BatchRendererTest, DoesNotCallFlushOnDestructor) { EXPECT_CALL(*mock, flush()).Times(0); renderer.reset(); testing::Mock::VerifyAndClearExpectations(mock); } TEST_F(BatchRendererTest, ClearPassesToClear) { EXPECT_CALL(*mock, flush()).Times(AnyNumber()); EXPECT_CALL(*mock, clear()).Times(1); renderer->clear(); } TEST_F(BatchRendererTest, FlushDoesNotPassToFlush) { EXPECT_CALL(*mock, flush()).Times(0); renderer->flush(); } TEST_F(BatchRendererTest, DoesNotWriteImmediately) { EXPECT_CALL(*mock, draw(_, _, _)).Times(0); renderer->draw(cast(1), math::matr4(), {}); testing::Mock::VerifyAndClearExpectations(mock); // We need to re-enact this expectation EXPECT_CALL(*mock, flush()).Times(AnyNumber()); EXPECT_CALL(*mock, draw(_, _, _)).Times(1); } TEST_F(BatchRendererTest, GroupsDataTogetherByMaterial) { EXPECT_CALL(*mock, flush()).Times(AnyNumber()); EXPECT_CALL(*mock, draw(cast(1), _, SizeIs(12))).Times(1); EXPECT_CALL(*mock, draw(cast(2), _, SizeIs(6))).Times(1); renderer->draw(DemoObject()); renderer->draw(DemoObject()); renderer->draw(DemoObject(2)); } TEST_F(BatchRendererTest, BatchLimitingCanSplitTheWrites) { EXPECT_CALL(*mock, flush()).Times(AnyNumber()); renderer.reset(new graphics::batch_renderer(drenderer.get(), 15)); // This sometimes occurs... EXPECT_CALL(*mock, draw(_, _, SizeIs(0))).Times(AnyNumber()); EXPECT_CALL(*mock, draw(cast(1), _, SizeIs(12))).Times(1); EXPECT_CALL(*mock, draw(cast(2), _, SizeIs(6))).Times(2); renderer->draw(DemoObject()); renderer->draw(DemoObject()); renderer->draw(DemoObject(2)); renderer->draw(DemoObject(2)); } struct InjectRendererTest : testing::Test { protected: void SetUp() override; void TearDown() override; mock_renderer_impl * mock; private: graphics::renderer_impl_factory::scoped_binding scope_; }; void InjectRendererTest::SetUp() { mock = new mock_renderer_impl; auto & factory = graphics::renderer_impl_factory::instance(); scope_ = factory.bind_scoped("mock", [this]() { return mock; }); } void InjectRendererTest::TearDown() { delete mock; } TEST_F(InjectRendererTest, ThrowsOnRequestOfBlank) { EXPECT_THROW(graphics::direct_renderer(""), graphics::unmapped_enum); } TEST(UnboundRendererTest, ThrowsOnRequestOfUnboundDriver) { EXPECT_THROW(graphics::direct_renderer("mock"), graphics::unmapped_enum); } TEST_F(InjectRendererTest, SuccessfulConstructionWithBoundImpl) { EXPECT_NO_THROW(graphics::direct_renderer("mock")); } TEST_F(InjectRendererTest, BoundImplIsSamePointed) { graphics::direct_renderer renderer("mock"); EXPECT_CALL(*mock, draw(cast(1), math::matr4(), IsEmpty())).Times(1); renderer.draw(cast(1), math::matr4(), {}); }