diff --git a/filament/backend/include/private/backend/CommandStream.h b/filament/backend/include/private/backend/CommandStream.h index f722794b52..fe8842e709 100644 --- a/filament/backend/include/private/backend/CommandStream.h +++ b/filament/backend/include/private/backend/CommandStream.h @@ -50,7 +50,7 @@ #include // Set to true to print every command out on log.d. This requires RTTI and DEBUG -#define DEBUG_COMMAND_STREAM false +#define DEBUG_COMMAND_STREAM true namespace filament::backend { diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 0ff03049a7..10b48c3181 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -240,6 +240,25 @@ function(add_demo NAME) endif() endfunction() +function(add_demo2 NAME) + include_directories(${GENERATION_ROOT}) + if (APPLE) + add_executable(${NAME} helper.h helper.mm ${NAME}.cpp) + target_link_libraries(${NAME} PRIVATE sample-resources) + target_compile_options(${NAME} PRIVATE ${COMPILER_FLAGS}) + set_target_properties(${NAME} PROPERTIES FOLDER Samples) + + # This is needed after XCode 15.3 + target_link_libraries(${NAME} PUBLIC "-framework Cocoa -framework QuartzCore" sdl2) + set_target_properties(${NAME} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE) + set_target_properties(${NAME} PROPERTIES INSTALL_RPATH /usr/local/lib) + else() + add_demo(${NAME}) + endif() + target_link_libraries(${NAME} PRIVATE gltfio) +endfunction() + + if (NOT ANDROID) add_demo(animation) add_demo(depthtesting) @@ -249,7 +268,7 @@ if (NOT ANDROID) add_demo(heightfield) add_demo(hellomorphing) add_demo(hellopbr) - add_demo(hellotriangle) + add_demo2(hellotriangle) add_demo(helloskinning) add_demo(helloskinningbuffer) add_demo(helloskinningbuffer_morebones) diff --git a/samples/hellotriangle.cpp b/samples/hellotriangle.cpp index 4477429dd6..4c36c84524 100644 --- a/samples/hellotriangle.cpp +++ b/samples/hellotriangle.cpp @@ -1,192 +1,191 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +#include +#include #include #include #include -#include -#include +#include #include +#include #include #include -#include -#include #include +#include + +#include #include +#include -#include -#include +#include -#include +#include +#include -#include -#include +#include +#include +#include -#include "generated/resources/resources.h" +#if defined(__linux__) +void* getNativeWindow(SDL_Window* sdlWindow) { + SDL_SysWMinfo wmi; + SDL_VERSION(&wmi.version); + SDL_GetWindowWMInfo(sdlWindow, &wmi); + if (wmi.subsystem == SDL_SYSWM_X11) { + Window win = (Window) wmi.info.x11.window; + return (void*) win; + } else { + std::cout << "Unknown SDL subsystem"; + } + return nullptr; +} +#elif defined(__APPLE__) + +#include "helper.h" + +#endif + +SDL_Window* createSDLwindow() { + uint32_t windowFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; + SDL_Window* win = SDL_CreateWindow("Hello World!", 100, 100, 600, 400, 0); + if (win == nullptr) { + std::cout << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl; + SDL_Quit(); + return nullptr; + } + + return win; +} using namespace filament; -using utils::Entity; -using utils::EntityManager; - -struct App { - Config config; - VertexBuffer* vb; - IndexBuffer* ib; - Material* mat; - Camera* cam; - Entity camera; - Skybox* skybox; - Entity renderable; -}; - -struct Vertex { - filament::math::float2 position; - uint32_t color; -}; - -static const Vertex TRIANGLE_VERTICES[3] = { - {{1, 0}, 0xffff0000u}, - {{cos(M_PI * 2 / 3), sin(M_PI * 2 / 3)}, 0xff00ff00u}, - {{cos(M_PI * 4 / 3), sin(M_PI * 4 / 3)}, 0xff0000ffu}, -}; - -static constexpr uint16_t TRIANGLE_INDICES[3] = { 0, 1, 2 }; - -static void printUsage(char* name) { - std::string exec_name(utils::Path(name).getName()); - std::string usage( - "HELLOTRIANGLE renders a spinning colored triangle\n" - "Usage:\n" - " HELLOTRIANGLE [options]\n" - "Options:\n" - " --help, -h\n" - " Prints this message\n\n" - " --api, -a\n" - " Specify the backend API: opengl, vulkan, or metal\n" - ); - const std::string from("HELLOTRIANGLE"); - for (size_t pos = usage.find(from); pos != std::string::npos; pos = usage.find(from, pos)) { - usage.replace(pos, from.length(), exec_name); +using namespace math; +using namespace utils; +int main() { + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + std::cout << "SDL_Init Error: " << SDL_GetError() << std::endl; + return 1; } - std::cout << usage; -} + const static uint32_t indices[] = { 0, 1, 2, }; -static int handleCommandLineArguments(int argc, char* argv[], App* app) { - static constexpr const char* OPTSTR = "ha:"; - static const struct option OPTIONS[] = { - { "help", no_argument, nullptr, 'h' }, - { "api", required_argument, nullptr, 'a' }, - { nullptr, 0, nullptr, 0 } + const static math::float3 vertices[] = { + { -10, 0, -10 }, + { -10, 0, 10 }, + { 10, 0, 10 }, }; - int opt; - int option_index = 0; - while ((opt = getopt_long(argc, argv, OPTSTR, OPTIONS, &option_index)) >= 0) { - std::string arg(optarg ? optarg : ""); - switch (opt) { - default: - case 'h': - printUsage(argv[0]); - exit(0); - case 'a': - if (arg == "opengl") { - app->config.backend = Engine::Backend::OPENGL; - } else if (arg == "vulkan") { - app->config.backend = Engine::Backend::VULKAN; - } else if (arg == "metal") { - app->config.backend = Engine::Backend::METAL; - } else { - std::cerr << "Unrecognized backend. Must be 'opengl'|'vulkan'|'metal'.\n"; - exit(1); - } - break; + + short4 tbn = math::packSnorm16( + mat3f::packTangentFrame( + math::mat3f{ + float3{ 1.0f, 0.0f, 0.0f }, + float3{ 0.0f, 0.0f, 1.0f }, float3{ 0.0f, 1.0f, 0.0f } }) + .xyzw); + + const static math::short4 normals[]{ tbn, tbn, tbn }; + SDL_Window* window = createSDLwindow(); + if (!window) { + return 1; + } + Engine* engine = Engine::create(filament::backend::Backend::VULKAN); + SwapChain* swapChain = engine->createSwapChain(getNativeWindow(window)); + Renderer* renderer = engine->createRenderer(); + + auto cameraEntity = EntityManager::get().create(); + Camera* camera = engine->createCamera(cameraEntity); + View* view = engine->createView(); + Scene* scene = engine->createScene(); + + view->setCamera(camera); + // Determine the current size of the window in physical pixels. + uint32_t w, h; + SDL_GL_GetDrawableSize(window, (int*) &w, (int*) &h); + camera->lookAt(float3(0, 50.5f, 0), float3(0, 0, 0), float3(1.f, 0, 0)); + camera->setProjection(45.0, double(w) / h, 0.1, 50, Camera::Fov::VERTICAL); + view->setViewport({ 0, 0, w, h }); + view->setScene(scene); + view->setPostProcessingEnabled(false); + + VertexBuffer* vertexBuffer = + VertexBuffer::Builder() + .vertexCount(3) + .bufferCount(2) + .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT3) + .attribute(VertexAttribute::TANGENTS, 1, VertexBuffer::AttributeType::SHORT4) + .normalized(VertexAttribute::TANGENTS) + .build(*engine); + + vertexBuffer->setBufferAt(*engine, 0, + VertexBuffer::BufferDescriptor(vertices, + vertexBuffer->getVertexCount() * sizeof(vertices[0]))); + vertexBuffer->setBufferAt(*engine, 1, + VertexBuffer::BufferDescriptor(normals, + vertexBuffer->getVertexCount() * sizeof(normals[0]))); + + IndexBuffer* indexBuffer = IndexBuffer::Builder().indexCount(3).build(*engine); + + indexBuffer->setBuffer(*engine, + IndexBuffer::BufferDescriptor(indices, + indexBuffer->getIndexCount() * sizeof(uint32_t))); + + filamat::MaterialBuilder::init(); + filamat::MaterialBuilder builder; + builder.name("Material") + .material(" void material(inout MaterialInputs material) {\n" + " prepareMaterial(material);" + " material.baseColor.rgb = materialParams.baseColor;" + " }") + .parameter("baseColor", filament::backend::UniformType::FLOAT3) + .parameter("metallic", filament::backend::UniformType::FLOAT) + .parameter("roughness", filament::backend::UniformType::FLOAT) + .parameter("reflectance", filament::backend::UniformType::FLOAT) + .optimization(filamat::MaterialBuilder::Optimization::NONE) + .shading(filamat::MaterialBuilder::Shading::UNLIT) + .targetApi(filamat::MaterialBuilder::TargetApi::ALL) + .platform(filamat::MaterialBuilder::Platform::ALL); + + filamat::Package package = builder.build(engine->getJobSystem()); + + Material* material = + Material::Builder().package(package.getData(), package.getSize()).build(*engine); + material->setDefaultParameter("baseColor", RgbType::LINEAR, float3{ 1, 0, 0 }); + material->setDefaultParameter("metallic", 0.0f); + material->setDefaultParameter("roughness", 0.4f); + material->setDefaultParameter("reflectance", 0.5f); + + MaterialInstance* materialInstance = material->createInstance(); + Entity renderable = EntityManager::get().create(); + + // build a quad + RenderableManager::Builder(1) + .boundingBox({ { -1, -1, -1 }, { 1, 1, 1 } }) + .material(0, materialInstance) + .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vertexBuffer, indexBuffer, 0, + 3) + .culling(false) + .build(*engine, renderable); + scene->addEntity(renderable); + + int i = 0; + while (i++ < 1) { + // beginFrame() returns false if we need to skip a frame + if (renderer->beginFrame(swapChain)) { + // for each View + renderer->render(view); + renderer->endFrame(); } } - return optind; -} - -int main(int argc, char** argv) { - App app{}; - app.config.title = "hellotriangle"; - app.config.featureLevel = backend::FeatureLevel::FEATURE_LEVEL_0; - handleCommandLineArguments(argc, argv, &app); - - auto setup = [&app](Engine* engine, View* view, Scene* scene) { - app.skybox = Skybox::Builder().color({0.1, 0.125, 0.25, 1.0}).build(*engine); - scene->setSkybox(app.skybox); - view->setPostProcessingEnabled(false); - static_assert(sizeof(Vertex) == 12, "Strange vertex size."); - app.vb = VertexBuffer::Builder() - .vertexCount(3) - .bufferCount(1) - .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 12) - .attribute(VertexAttribute::COLOR, 0, VertexBuffer::AttributeType::UBYTE4, 8, 12) - .normalized(VertexAttribute::COLOR) - .build(*engine); - app.vb->setBufferAt(*engine, 0, - VertexBuffer::BufferDescriptor(TRIANGLE_VERTICES, 36, nullptr)); - app.ib = IndexBuffer::Builder() - .indexCount(3) - .bufferType(IndexBuffer::IndexType::USHORT) - .build(*engine); - app.ib->setBuffer(*engine, - IndexBuffer::BufferDescriptor(TRIANGLE_INDICES, 6, nullptr)); - app.mat = Material::Builder() - .package(RESOURCES_BAKEDCOLOR_DATA, RESOURCES_BAKEDCOLOR_SIZE) - .build(*engine); - app.renderable = EntityManager::get().create(); - RenderableManager::Builder(1) - .boundingBox({{ -1, -1, -1 }, { 1, 1, 1 }}) - .material(0, app.mat->getDefaultInstance()) - .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, app.vb, app.ib, 0, 3) - .culling(false) - .receiveShadows(false) - .castShadows(false) - .build(*engine, app.renderable); - scene->addEntity(app.renderable); - app.camera = utils::EntityManager::get().create(); - app.cam = engine->createCamera(app.camera); - view->setCamera(app.cam); - }; - - auto cleanup = [&app](Engine* engine, View*, Scene*) { - engine->destroy(app.skybox); - engine->destroy(app.renderable); - engine->destroy(app.mat); - engine->destroy(app.vb); - engine->destroy(app.ib); - engine->destroyCameraComponent(app.camera); - utils::EntityManager::get().destroy(app.camera); - }; - - FilamentApp::get().animate([&app](Engine* engine, View* view, double now) { - constexpr float ZOOM = 1.5f; - const uint32_t w = view->getViewport().width; - const uint32_t h = view->getViewport().height; - const float aspect = (float) w / h; - app.cam->setProjection(Camera::Projection::ORTHO, - -aspect * ZOOM, aspect * ZOOM, - -ZOOM, ZOOM, 0, 1); - auto& tcm = engine->getTransformManager(); - tcm.setTransform(tcm.getInstance(app.renderable), - filament::math::mat4f::rotation(now, filament::math::float3{ 0, 0, 1 })); - }); - - FilamentApp::get().run(app.config, setup, cleanup); + bool running = true; + SDL_Event event; + while (running) { + while (SDL_PollEvent(&event)) { + if (event.type == SDL_QUIT) { + running = false; + } + SDL_Delay(16); + } + } + + engine->destroy(cameraEntity); return 0; } diff --git a/samples/helper.h b/samples/helper.h new file mode 100644 index 0000000000..c2dd42babe --- /dev/null +++ b/samples/helper.h @@ -0,0 +1,10 @@ +#ifndef HELLO_TRIANGLE_H +#define HELLO_TRIANGLE_H + +#include +#include +#include + +void* getNativeWindow(SDL_Window* sdlWindow); + +#endif diff --git a/samples/helper.mm b/samples/helper.mm new file mode 100644 index 0000000000..fd4db4ea80 --- /dev/null +++ b/samples/helper.mm @@ -0,0 +1,39 @@ +#include "helper.h" +#include +#include + +#include + +void* getNativeWindow(SDL_Window* sdlWindow) { + SDL_SysWMinfo wmi; + SDL_VERSION(&wmi.version); + FILAMENT_CHECK_POSTCONDITION(SDL_GetWindowWMInfo(sdlWindow, &wmi)) + << "SDL version unsupported!"; + NSWindow* win = wmi.info.cocoa.window; + NSView* view = [win contentView]; + + [win setColorSpace:[NSColorSpace sRGBColorSpace]]; + + [view setWantsLayer:YES]; + CAMetalLayer* metalLayer = [CAMetalLayer layer]; + metalLayer.bounds = view.bounds; + + // It's important to set the drawableSize to the actual backing pixels. When rendering + // full-screen, we can skip the macOS compositor if the size matches the display size. + metalLayer.drawableSize = [view convertSizeToBacking:view.bounds.size]; + + // In its implementation of vkGetPhysicalDeviceSurfaceCapabilitiesKHR, MoltenVK takes into + // consideration both the size (in points) of the bounds, and the contentsScale of the + // CAMetalLayer from which the Vulkan surface was created. + // See also https://github.com/KhronosGroup/MoltenVK/issues/428 + metalLayer.contentsScale = view.window.backingScaleFactor; + + // This is set to NO by default, but is also important to ensure we can bypass the compositor + // in full-screen mode + // See "Direct to Display" http://metalkit.org/2017/06/30/introducing-metal-2.html. + metalLayer.opaque = YES; + + [view setLayer:metalLayer]; + return metalLayer; +} +