// // manager_test.cxx // graphics // // Created by Sam Jaffe on 6/6/19. // Copyright © 2019 Sam Jaffe. All rights reserved. // #include "game/graphics/manager.hpp" #include #include "../src/helper.hpp" #include "game/graphics/exception.h" #include "game/graphics/material.hpp" #include "game/graphics/shader.hpp" #include "game/graphics/shader_program.hpp" #include "game/graphics/texture.hpp" #ifdef __APPLE__ namespace env::osx { extern void bundle(std::string const &); } #endif using testing::_; using testing::AllOf; using testing::AnyNumber; using testing::Eq; using testing::Ge; using testing::Le; using testing::Ne; using testing::SizeIs; struct mock_manager_impl : graphics::manager { using shader = graphics::shader; using shader_program = graphics::shader_program; using texture = graphics::texture; MOCK_CONST_METHOD2(compile_shader, shader(graphics::shaders::type, std::string const &)); MOCK_CONST_METHOD2(compile_program, shader_program(identity, identity)); shader compile(graphics::shaders::type t, std::string const & s) const { return compile_shader(t, s); } shader_program compile(identity f, identity v) const { return compile_program(f, v); } MOCK_CONST_METHOD3(compile, texture(graphics::textures::format, math::vec2i, void const *)); }; template identity cast(unsigned int id) { return *reinterpret_cast *>(&id); } auto cast_p(unsigned int id) { return cast(id); } auto cast_s(unsigned int id) { return cast(id); } class ManagerTest : public testing::Test { public: void SetUp() override; mock_manager_impl mock; private: struct { graphics::texture operator()(math::vec2i const & sz) { return {++i, sz}; } unsigned int i{0}; } next_tex; struct { graphics::shader operator()(graphics::shaders::type tp, std::string const & path) { return {++i, tp, path}; } unsigned int i{0}; } next_shader; struct { graphics::shader_program operator()(identity f, identity v) { return {++i, f, v}; } unsigned int i{0}; } next_program; }; void ManagerTest::SetUp() { #ifdef __APPLE__ env::osx::bundle("leumasjaffe.graphics-test"); #endif using testing::Invoke; using testing::WithArg; // Start with a new set of IDs ON_CALL(mock, compile(_, _, _)) .WillByDefault(WithArg<1>(Invoke(std::ref(next_tex)))); ON_CALL(mock, compile_shader(_, _)) .WillByDefault(Invoke(std::ref(next_shader))); ON_CALL(mock, compile_program(_, _)) .WillByDefault(Invoke(std::ref(next_program))); } TEST_F(ManagerTest, DoesNotGreedilyCompile) { EXPECT_CALL(mock, compile(_, _, _)).Times(0); EXPECT_CALL(mock, compile_shader(_, _)).Times(0); EXPECT_CALL(mock, compile_program(_, _)).Times(0); } using TextureTest = ManagerTest; TEST_F(TextureTest, ThrowsOnNonExistantFile) { EXPECT_THROW(mock.get("resources/missing.png"), std::runtime_error); } TEST_F(TextureTest, NoExceptIfFileExists) { EXPECT_CALL(mock, compile(_, math::vec2i(1, 1), _)).Times(1); EXPECT_NO_THROW(mock.get("resources/black.bmp")); } TEST_F(TextureTest, CreatedTextureCanBeRefetched) { EXPECT_CALL(mock, compile(_, math::vec2i(1, 1), _)).Times(1); auto id_1 = mock.get("resources/black.bmp"); auto id_2 = mock.get("resources/black.bmp"); EXPECT_THAT(id_1, Eq(id_2)); } TEST_F(TextureTest, CanGetTextureFromId) { EXPECT_CALL(mock, compile(_, math::vec2i(1, 1), _)).Times(1); auto texture_id = mock.get("resources/black.bmp"); auto texture = mock.get(texture_id); EXPECT_THAT(texture, Eq(texture_id)); } TEST_F(TextureTest, SizeOfTexturePassedIntoCompile) { EXPECT_CALL(mock, compile(_, math::vec2i(1, 1), _)).Times(0); EXPECT_CALL(mock, compile(_, math::vec2i(2, 2), _)).Times(1); mock.get("resources/black2.bmp"); } TEST_F(TextureTest, CanReadRGBFormat) { using graphics::textures::format; EXPECT_CALL(mock, compile(format::RGB, math::vec2i(1, 1), _)).Times(1); mock.get("resources/black_rgb.png"); } TEST_F(TextureTest, CanReadRGBAFormat) { using graphics::textures::format; EXPECT_CALL(mock, compile(format::RGBA, math::vec2i(1, 1), _)).Times(1); mock.get("resources/black_rgba.png"); } using MaterialTest = ManagerTest; TEST_F(MaterialTest, ThrowsExceptionIfNoTexOrUniform) { EXPECT_CALL(mock, compile(_, _, _)).Times(AnyNumber()); EXPECT_THROW(mock.get(cast_p(1), "", ""), graphics::unmapped_enum); } TEST_F(MaterialTest, GeneratesUniformTexturesIfNoTexFile) { using graphics::materials::uniform; EXPECT_CALL(mock, compile(_, math::vec2i(1, 1), _)).Times(3); EXPECT_NO_THROW(mock.get(cast_p(1), "", "u_normalMap")); } TEST_F(MaterialTest, CreatedMaterialCanBeRefetched) { // Three times and no more EXPECT_CALL(mock, compile(_, math::vec2i(1, 1), _)).Times(3); auto id_1 = mock.get(cast_p(1), "", "u_normalMap"); auto id_2 = mock.get(cast_p(1), "", "u_normalMap"); EXPECT_THAT(id_1, Eq(id_2)); } TEST_F(MaterialTest, CanGetMaterialFromId) { auto material_id = mock.get(cast_p(1), "", "u_normalMap"); auto material = mock.get(material_id); // Ensure that the material is the 'same one' i.e. // mock.get(mock.get(id)) == mock.get(id) EXPECT_THAT(material, Eq(material_id)); } TEST_F(MaterialTest, UniformMaterialIsOneByOne) { auto material = mock.get(mock.get(cast_p(1), "", "u_normalMap")); // Uniforms are always sized 1x1 EXPECT_THAT(material.size, Eq(math::vec2i(1, 1))); } TEST_F(MaterialTest, SizeCapturesTextureSize) { auto material = mock.get(mock.get(cast_p(1), "resources/black2.bmp", "u_normalMap")); EXPECT_THAT(material.size, Eq(math::vec2i(2, 2))); } TEST_F(MaterialTest, UniformMaterialHasDataBindingToNormalTex) { using graphics::materials::uniform; auto material = mock.get(mock.get(cast_p(1), "", "u_normalMap")); EXPECT_THAT(material.uniforms, SizeIs(1)); // Because we never initialize any textures, we are within the first three // texture ID units. EXPECT_THAT(material.uniforms[0].texture.id, AllOf(Ge(1), Le(3))); // Test the mapping from "u_normalMap" to uniform::NORMAL EXPECT_THAT(material.uniforms[0].uniform_id, Eq(uniform::NORMAL)); } TEST_F(MaterialTest, DifferentProgramMakesDifferentMaterial) { EXPECT_CALL(mock, compile(_, math::vec2i(1, 1), _)).Times(3); auto id_1 = mock.get(cast_p(1), "", "u_normalMap"); auto id_2 = mock.get(cast_p(2), "", "u_normalMap"); EXPECT_THAT(id_1, Ne(id_2)); } TEST_F(MaterialTest, DifferentProgramDoesntChangeUniformId) { EXPECT_CALL(mock, compile(_, math::vec2i(1, 1), _)).Times(3); auto mat_1 = mock.get(mock.get(cast_p(1), "", "u_normalMap")); auto mat_2 = mock.get(mock.get(cast_p(2), "", "u_normalMap")); EXPECT_THAT(mat_1.uniforms[0].texture, Eq(mat_2.uniforms[0].texture)); } TEST_F(MaterialTest, TexFileSkipsUniformLoad) { using graphics::materials::uniform; EXPECT_CALL(mock, compile(_, math::vec2i(1, 1), _)).Times(1); auto material = mock.get(mock.get(cast_p(1), "resources/black.bmp", "u_normalMap")); EXPECT_THAT(material.uniforms, SizeIs(1)); // Because we never initialize any textures, we are within the first three // texture ID units. EXPECT_THAT(material.uniforms[0].texture.id, Eq(1)); // Test the mapping from "u_normalMap" to uniform::NORMAL EXPECT_THAT(material.uniforms[0].uniform_id, Eq(uniform::NORMAL)); } TEST_F(MaterialTest, MissingFileNoExcept) { EXPECT_NO_THROW( mock.get(mock.get(cast_p(1), "resources/missing.png", "u_normalMap"))); } TEST_F(MaterialTest, MissingFileFallsBackOnUniform) { using graphics::materials::uniform; auto material = mock.get(mock.get(cast_p(1), "resources/missing.png", "u_normalMap")); EXPECT_THAT(material.uniforms, SizeIs(1)); // Because we never initialize any textures, we are within the first three // texture ID units. EXPECT_THAT(material.uniforms[0].texture.id, AllOf(Ge(1), Le(3))); // Test the mapping from "u_normalMap" to uniform::NORMAL EXPECT_THAT(material.uniforms[0].uniform_id, Eq(uniform::NORMAL)); } using ShaderTest = ManagerTest; TEST_F(ShaderTest, CanCreateShaders) { using graphics::shaders::type; EXPECT_CALL(mock, compile_shader(_, _)).Times(1); EXPECT_NO_THROW(mock.get(type::FRAGMENT, "A")); } TEST_F(ShaderTest, CreatedShaderCanBeRefetched) { using graphics::shaders::type; EXPECT_CALL(mock, compile_shader(_, _)).Times(1); auto id_1 = mock.get(type::FRAGMENT, "A"); auto id_2 = mock.get(type::FRAGMENT, "A"); EXPECT_THAT(id_1, Eq(id_2)); } TEST_F(ShaderTest, DifferentTypeMakesDifferentShader) { using graphics::shaders::type; EXPECT_CALL(mock, compile_shader(_, _)).Times(2); auto id_1 = mock.get(type::FRAGMENT, "A"); auto id_2 = mock.get(type::VERTEX, "A"); EXPECT_THAT(id_1, Ne(id_2)); } using ShaderProgramTest = ManagerTest; TEST_F(ShaderProgramTest, CreatingProgramCreatesTwoShaders) { EXPECT_CALL(mock, compile_shader(_, _)).Times(2); EXPECT_CALL(mock, compile_program(cast_s(1), cast_s(2))).Times(1); EXPECT_NO_THROW(mock.get("A", "B")); } TEST_F(ShaderProgramTest, CreatedProgramCanBeRefetched) { EXPECT_CALL(mock, compile_shader(_, _)).Times(2); EXPECT_CALL(mock, compile_program(cast_s(1), cast_s(2))).Times(1); auto id_1 = mock.get("A", "B"); auto id_2 = mock.get("A", "B"); EXPECT_THAT(id_1, Eq(id_2)); }