#include #include "BsApplication.h" #include "BsImporter.h" #include "BsTextureImportOptions.h" #include "BsMaterial.h" #include "BsShader.h" #include "BsTechnique.h" #include "BsPass.h" #include "BsCoreThreadAccessor.h" #include "BsApplication.h" #include "BsVirtualInput.h" #include "BsCCamera.h" #include "BsCRenderable.h" #include "BsCGUIWidget.h" #include "BsGUILayoutX.h" #include "BsGUILayoutY.h" #include "BsGUISpace.h" #include "BsGUILabel.h" #include "BsGUIButton.h" #include "BsGUIListBox.h" #include "BsBuiltinResources.h" #include "BsRTTIType.h" #include "BsHString.h" #include "BsRenderWindow.h" #include "BsSceneObject.h" #include "BsCoreThread.h" #include "BsProfilerOverlay.h" #include "BsCoreRenderer.h" #include "BsResources.h" #include "BsGUIPanel.h" #include "CameraFlyer.h" namespace BansheeEngine { UINT32 windowResWidth = 1280; UINT32 windowResHeight = 720; /** * Imports all of our assets and prepares GameObject that handle the example logic. */ void setUpExample(); /** * Import mesh/texture/GPU programs used by the example. */ void importAssets(HMesh& model, HTexture& texture, HShader& shader); /** * Create a material used by our example model. */ HMaterial createMaterial(const HTexture& texture, const HShader& shader); /** * Set up example scene objects. */ void setUp3DScene(const HMesh& mesh, const HMaterial& material); /** * Set up example GUI. */ void setUpGUI(); /** * Set up input configuration and callbacks. */ void setUpInput(); /** * Toggles the primary window between full-screen and windowed mode. */ void toggleFullscreen(); /** * Called whenever the main render window is resized. */ void renderWindowResized(); /** * Called when the selected video mode changes in the video mode list box. */ void videoModeChanged(UINT32 idx); /** * Triggered whenever a virtual button is released. */ void buttonUp(const VirtualButton& button, UINT32 deviceIdx); } using namespace BansheeEngine; /** * Main entry point into the application. */ int CALLBACK WinMain( _In_ HINSTANCE hInstance, _In_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow ) { // Descriptor used for initializing the primary application window. RENDER_WINDOW_DESC renderWindowDesc; renderWindowDesc.videoMode = VideoMode(windowResWidth, windowResHeight); renderWindowDesc.title = "Banshee Example App"; renderWindowDesc.fullscreen = false; // List of importer plugins we plan on using for importing various resources Vector importers; importers.push_back("BansheeFreeImgImporter"); // For importing textures importers.push_back("BansheeFBXImporter"); // For importing meshes importers.push_back("BansheeFontImporter"); // For importing fonts importers.push_back("BansheeSL"); // For importing shaders // Initializes the application with primary window defined as above and DirectX 11 render system. // You may use other render systems than DirectX 11, however this example for simplicity only uses DirectX 11. // If you wanted other render systems you would need to create separate shaders for them and import them // along with (or replace) the DX11 ones. Application::startUp(renderWindowDesc, RenderAPIPlugin::DX11, RendererPlugin::Default, importers); // Imports all of ours assets and prepares GameObject that handle the example logic. setUpExample(); // Runs the main loop that does most of the work. This method will exit when user closes the main // window or exits in some other way. Application::instance().runMainLoop(); Application::shutDown(); return 0; } namespace BansheeEngine { Path dataPath = Application::findPath(RUNTIME_DATA_PATH); Path exampleModelPath = dataPath + "Examples\\Dragon.fbx"; Path exampleTexturePath = dataPath + "Examples\\Dragon.tga"; Path exampleShaderPath = dataPath + "Examples\\Example.bsl"; GUIButton* toggleFullscreenButton = nullptr; bool fullscreen = false; const VideoMode* selectedVideoMode = nullptr; Vector videoModes; HCamera sceneCamera; HProfilerOverlay profilerOverlay; VirtualButton toggleCPUProfilerBtn; VirtualButton toggleGPUProfilerBtn; bool cpuProfilerActive = false; bool gpuProfilerActive = false; void setUpExample() { HMesh exampleModel; HTexture exampleTexture; HShader exampleShader; importAssets(exampleModel, exampleTexture, exampleShader); HMaterial exampleMaterial = createMaterial(exampleTexture, exampleShader); setUp3DScene(exampleModel, exampleMaterial); setUpGUI(); setUpInput(); } void importAssets(HMesh& model, HTexture& texture, HShader& shader) { // Import mesh, texture and shader from the disk. In a normal application you would want to save the imported assets // so next time the application is ran you can just load them directly. This can be done with Resources::save/load. // Import an FBX mesh. model = Importer::instance().import(exampleModelPath); // When importing you may specify optional import options that control how is the asset imported. ImportOptionsPtr textureImportOptions = Importer::instance().createImportOptions(exampleTexturePath); // rtti_is_of_type checks if the import options are of valid type, in case the provided path is pointing to a non-texture resource. // This is similar to dynamic_cast but uses Banshee internal RTTI system for type checking. if (rtti_is_of_type(textureImportOptions)) { TextureImportOptions* importOptions = static_cast(textureImportOptions.get()); // We want maximum number of mipmaps to be generated importOptions->setGenerateMipmaps(true); } // Import texture with specified import options texture = Importer::instance().import(exampleTexturePath, textureImportOptions); // Import shader shader = Importer::instance().import(exampleShaderPath); } HMaterial createMaterial(const HTexture& texture, const HShader& shader) { // And finally create a material with the newly created shader HMaterial exampleMaterial = Material::create(shader); // And set the texture to be used by the "tex" shader parameter. We leave the "samp" // parameter at its defaults. exampleMaterial->setTexture("tex", texture); return exampleMaterial; } void setUp3DScene(const HMesh& mesh, const HMaterial& material) { /************************************************************************/ /* SCENE OBJECT */ /************************************************************************/ // Now we create a scene object that has a position, orientation, scale and optionally // components to govern its logic. In this particular case we are creating a SceneObject // with a Renderable component which will render a mesh at the position of the scene object // with the provided material. // Create new scene object at (0, 0, 0) HSceneObject dragonSO = SceneObject::create("Dragon"); // Attach the Renderable component and hook up the mesh we imported earlier, // and the material we created in the previous section. HRenderable renderable = dragonSO->addComponent(); renderable->setMesh(mesh); renderable->setMaterial(material); /************************************************************************/ /* CAMERA */ /************************************************************************/ // In order something to render on screen we need at least one camera. // Like before, we create a new scene object at (0, 0, 0). HSceneObject sceneCameraSO = SceneObject::create("SceneCamera"); // Get the primary render window we need for creating the camera. Additionally // hook up a callback so we are notified when user resizes the window. RenderWindowPtr window = gApplication().getPrimaryWindow(); window->onResized.connect(&renderWindowResized); // Add a Camera component that will output whatever it sees into that window // (You could also use a render texture or another window you created). sceneCamera = sceneCameraSO->addComponent(window); // Set up camera component properties // Priority determines in what order are cameras rendered in case multiple cameras render to the same render target. // We raise the priority slightly because later in code we have defined a GUI camera that we want to render second. sceneCamera->setPriority(1); // Set closest distance that is visible. Anything below that is clipped. sceneCamera->setNearClipDistance(5); // Set aspect ratio depending on the current resolution sceneCamera->setAspectRatio(windowResWidth / (float)windowResHeight); // Add a CameraFlyer component that allows us to move the camera. See CameraFlyer for more information. sceneCameraSO->addComponent(); // Position and orient the camera scene object sceneCameraSO->setPosition(Vector3(-130.0f, 140.0f, 650.0f)); sceneCameraSO->lookAt(Vector3(0, 0, 0)); } void setUpInput() { // Register input configuration // Banshee allows you to use VirtualInput system which will map input device buttons // and axes to arbitrary names, which allows you to change input buttons without affecting // the code that uses it, since the code is only aware of the virtual names. // If you want more direct input, see Input class. auto inputConfig = VirtualInput::instance().getConfiguration(); // Camera controls for buttons (digital 0-1 input, e.g. keyboard or gamepad button) inputConfig->registerButton("Forward", BC_W); inputConfig->registerButton("Back", BC_S); inputConfig->registerButton("Left", BC_A); inputConfig->registerButton("Right", BC_D); inputConfig->registerButton("Forward", BC_UP); inputConfig->registerButton("Back", BC_BACK); inputConfig->registerButton("Left", BC_LEFT); inputConfig->registerButton("Right", BC_RIGHT); inputConfig->registerButton("FastMove", BC_LSHIFT); inputConfig->registerButton("RotateCam", BC_MOUSE_RIGHT); // Camera controls for axes (analog input, e.g. mouse or gamepad thumbstick) // These return values in [-1.0, 1.0] range. inputConfig->registerAxis("Horizontal", VIRTUAL_AXIS_DESC((UINT32)InputAxis::MouseX)); inputConfig->registerAxis("Vertical", VIRTUAL_AXIS_DESC((UINT32)InputAxis::MouseY)); // Controls that toggle the profiler overlays inputConfig->registerButton("CPUProfilerOverlay", BC_F1); inputConfig->registerButton("GPUProfilerOverlay", BC_F2); // Cache the profiler overlay buttons so when a button is pressed we can quickly // use these to determine its the one we want toggleCPUProfilerBtn = VirtualButton("CPUProfilerOverlay"); toggleGPUProfilerBtn = VirtualButton("GPUProfilerOverlay"); // Hook up a callback that gets triggered whenever a virtual button is released VirtualInput::instance().onButtonUp.connect(&buttonUp); } void setUpGUI() { // Create a scene object that will contain GUI components HSceneObject guiSO = SceneObject::create("Example"); // Get the primary render window we need for creating the camera. RenderWindowPtr window = gApplication().getPrimaryWindow(); // First we want another camera that is responsible for rendering GUI HCamera guiCamera = guiSO->addComponent(window); // Set up GUI camera properties. // We don't care about aspect ratio for GUI camera. guiCamera->setAspectRatio(1.0f); // This camera should ignore any Renderable objects in the scene guiCamera->setLayers(0); // Don't clear this camera as that would clear anything the main camera has rendered. guiCamera->getViewport()->setRequiresClear(false, false, false); // Add a GUIWidget, the top-level GUI component, parent to all GUI elements. GUI widgets // require you to specify a viewport that they will output rendered GUI elements to. HGUIWidget gui = guiSO->addComponent(guiCamera); // Depth allows you to control how is a GUI widget rendered in relation to other widgets // Lower depth means the widget will be rendered in front of those with higher. In this case we just // make the depth mid-range as there are no other widgets. gui->setDepth(128); // GUI skin defines how are all child elements of the GUI widget renderered. It contains all their styles // and default layout properties. We use the default skin that comes built into Banshee. gui->setSkin(BuiltinResources::instance().getGUISkin()); // Get the primary GUI panel that stretches over the entire window and add to it a vertical layout // that will be using for vertically positioning messages about toggling profiler overlay. GUILayout* bottomLayout = gui->getPanel()->addNewElement(); // Add a flexible space that fills up any remaining area in the layout, making the two labels above be aligned // to the bottom of the GUI widget (and the screen). bottomLayout->addNewElement(); // Add a couple of labels to the layout with the needed messages. Labels expect a HString object that // maps into a string table and allows for easily localization. bottomLayout->addElement(GUILabel::create(HString(L"Press F1 to toggle CPU profiler overlay"))); bottomLayout->addElement(GUILabel::create(HString(L"Press F2 to toggle GPU profiler overlay"))); // Create a GUI panel that is used for displaying resolution and fullscreen options. GUILayout* rightLayout = gui->getPanel()->addNewElement(); rightLayout->setPosition(30, 30); // We want all the GUI elements be right aligned, so we add a flexible space first. rightLayout->addNewElement(); // And we want the elements to be vertically placed, top to bottom GUILayout* elemLayout = rightLayout->addNewElement(); // Add a button that will trigger a callback when clicked toggleFullscreenButton = GUIButton::create(HString(L"Toggle fullscreen")); toggleFullscreenButton->onClick.connect(&toggleFullscreen); elemLayout->addElement(toggleFullscreenButton); // Leave 30 pixels to the right free rightLayout->addNewElement(30); // Add a profiler overlay object that is responsible for displaying CPU and GPU profiling GUI profilerOverlay = guiSO->addComponent(guiCamera.getInternalPtr()); // Set up video mode list box // First get a list of output devices const VideoModeInfo& videoModeInfo = RenderAPI::getVideoModeInfo(); // Get video mode info for the primary monitor const VideoOutputInfo& primaryMonitorInfo = videoModeInfo.getOutputInfo(0); // Make the current desktop mode the default video mode selectedVideoMode = &primaryMonitorInfo.getDesktopVideoMode(); // Create list box elements for each available video mode UINT32 numVideoModes = primaryMonitorInfo.getNumVideoModes(); Vector videoModeLabels(numVideoModes); UINT32 selectedVideoModeIdx = 0; for (UINT32 i = 0; i < numVideoModes; i++) { const VideoMode& curVideoMode = primaryMonitorInfo.getVideoMode(i); HString videoModeLabel(L"{0} x {1} at {2}Hz"); videoModeLabel.setParameter(0, toWString(curVideoMode.getWidth())); videoModeLabel.setParameter(1, toWString(curVideoMode.getHeight())); videoModeLabel.setParameter(2, toWString(Math::roundToInt(curVideoMode.getRefreshRate()))); videoModeLabels[i] = videoModeLabel; videoModes.push_back(&curVideoMode); if (curVideoMode == *selectedVideoMode) selectedVideoModeIdx = i; } // Create the list box GUIListBox* videoModeListBox = GUIListBox::create(videoModeLabels); rightLayout->addElement(videoModeListBox); // Select the default (desktop) video mode videoModeListBox->selectElement(selectedVideoModeIdx); // Set up a callback to be notified when video mode changes videoModeListBox->onSelectionToggled.connect(&videoModeChanged); } void toggleFullscreen() { RenderWindowPtr window = gApplication().getPrimaryWindow(); // In order to toggle between full-screen and windowed mode we need to use a CoreAccessor. // Banshee is a multi-threaded engine and when you need to communicate between simulation and // core thread you will use a CoreAccessor. Calling a core accessor method will essentially // queue the method to be executed later. Since RenderWindow is a core object you need to use // CoreAccessor to modify and access it from simulation thread, except where noted otherwise. // Classes where it is not clear if they are to be used on the core or simulation thread have // it noted in their documentation. e.g. RenderWindow::setWindowed method is marked as "Core only". // Additional asserts are normally in place for debug builds which make it harder for you to accidentally // call something from the wrong thread. if (fullscreen) { window->setWindowed(gCoreAccessor(), windowResWidth, windowResHeight); } else { window->setFullscreen(gCoreAccessor(), *selectedVideoMode); } fullscreen = !fullscreen; } void renderWindowResized() { RenderWindowPtr window = gApplication().getPrimaryWindow(); const RenderWindowProperties& rwProps = window->getProperties(); if (!fullscreen) { windowResWidth = rwProps.getWidth(); windowResHeight = rwProps.getHeight(); } sceneCamera->setAspectRatio(rwProps.getWidth() / (float)rwProps.getHeight()); } void videoModeChanged(UINT32 idx, bool enabled) { selectedVideoMode = videoModes[idx]; if (fullscreen) { RenderWindowPtr window = gApplication().getPrimaryWindow(); window->setFullscreen(gCoreAccessor(), *selectedVideoMode); } } void buttonUp(const VirtualButton& button, UINT32 deviceIdx) { // Check if the pressed button is one of the either buttons we defined // in "setUpExample", and toggle profiler overlays accordingly. // Device index is ignored for now, as it is assumed the user is using a single keyboard, // but if you wanted support for multiple gamepads you would check deviceIdx. if (button == toggleCPUProfilerBtn) { if (cpuProfilerActive) { profilerOverlay->hide(); cpuProfilerActive = false; } else { profilerOverlay->show(ProfilerOverlayType::CPUSamples); cpuProfilerActive = true; gpuProfilerActive = false; } } else if (button == toggleGPUProfilerBtn) { if (gpuProfilerActive) { profilerOverlay->hide(); gpuProfilerActive = false; } else { profilerOverlay->show(ProfilerOverlayType::GPUSamples); gpuProfilerActive = true; cpuProfilerActive = false; } } } }