Skip to content

Commit

Permalink
Add initial QtQuick implementation by sharing a surface
Browse files Browse the repository at this point in the history
  • Loading branch information
nolankramer committed Dec 23, 2024
1 parent d5744e3 commit 1d3af3b
Show file tree
Hide file tree
Showing 10 changed files with 513 additions and 12 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ vsg_setup_build_vars()
find_package(vsgXchange) # only used by examples

# if Qt5 then we need 5.10 or later
find_package(${QT_PACKAGE_NAME} COMPONENTS Widgets REQUIRED)
find_package(${QT_PACKAGE_NAME} COMPONENTS Widgets Quick QuickWidgets REQUIRED)

vsg_add_target_clang_format(
FILES
Expand Down
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
add_subdirectory(vsgqtmultiviewer)
add_subdirectory(vsgqtviewer)
add_subdirectory(vsgqtwidgets)
add_subdirectory(vsgqtwindows)
19 changes: 19 additions & 0 deletions examples/vsgqtwidgets/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
set(SOURCES
main.cpp
)

set(HEADERS
)

add_executable(vsgqtwidgets ${MODE} ${SOURCES} ${HEADERS} ${FORMS})

target_link_libraries(vsgqtwidgets vsgQt6)

if (vsgXchange_FOUND)
target_compile_definitions(vsgqtwidgets PRIVATE vsgXchange_FOUND)
target_link_libraries(vsgqtwidgets vsgXchange::vsgXchange)
endif()


install(TARGETS vsgqtwidgets DESTINATION bin)

142 changes: 142 additions & 0 deletions examples/vsgqtwidgets/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#include <vsg/all.h>

#ifdef vsgXchange_FOUND
# include <vsgXchange/all.h>
#endif

#include <QtWidgets/QApplication>
#include <QtWidgets/QMainWindow>

#include <vsgQt6/Widget.h>

#include <iostream>

vsgQt6::Widget* createWidget(vsg::ref_ptr<vsgQt6::Viewer> viewer, vsg::ref_ptr<vsg::WindowTraits> traits, vsg::ref_ptr<vsg::Node> vsg_scene, QWidget* parent, const QString& title = {})
{
auto widget = new vsgQt6::Widget(viewer, parent);

widget->initialize();

// if this is the first window to be created, use its device for future window creation.
if (!traits->device) traits->device = widget->windowAdapter->getOrCreateDevice();

// compute the bounds of the scene graph to help position camera
vsg::ComputeBounds computeBounds;
vsg_scene->accept(computeBounds);
vsg::dvec3 centre = (computeBounds.bounds.min + computeBounds.bounds.max) * 0.5;
double radius = vsg::length(computeBounds.bounds.max - computeBounds.bounds.min) * 0.6;
double nearFarRatio = 0.001;

uint32_t width = widget->width();
uint32_t height = widget->height();

vsg::ref_ptr<vsg::EllipsoidModel> ellipsoidModel(vsg_scene->getObject<vsg::EllipsoidModel>("EllipsoidModel"));
vsg::ref_ptr<vsg::Camera> camera;
{
// set up the camera
auto lookAt = vsg::LookAt::create(centre + vsg::dvec3(0.0, -radius * 3.5, 0.0), centre, vsg::dvec3(0.0, 0.0, 1.0));

vsg::ref_ptr<vsg::ProjectionMatrix> perspective;
if (ellipsoidModel)
{
perspective = vsg::EllipsoidPerspective::create(
lookAt, ellipsoidModel, 30.0,
static_cast<double>(width) /
static_cast<double>(height),
nearFarRatio, false);
}
else
{
perspective = vsg::Perspective::create(
30.0,
static_cast<double>(width) /
static_cast<double>(height),
nearFarRatio * radius, radius * 4.5);
}

camera = vsg::Camera::create(perspective, lookAt, vsg::ViewportState::create(VkExtent2D{width, height}));
}

auto trackball = vsg::Trackball::create(camera, ellipsoidModel);
trackball->addWindow(*widget);

viewer->addEventHandler(trackball);

auto commandGraph = vsg::createCommandGraphForView(*widget, camera, vsg_scene);

viewer->addRecordAndSubmitTaskAndPresentation({commandGraph});

return widget;
}

int main(int argc, char* argv[])
{
QApplication application(argc, argv);

vsg::CommandLine arguments(&argc, argv);

// set up vsg::Options to pass in filepaths, ReaderWriters and other IO
// related options to use when reading and writing files.
auto options = vsg::Options::create();
options->fileCache = vsg::getEnv("VSG_FILE_CACHE");
options->paths = vsg::getEnvPaths("VSG_FILE_PATH");
#ifdef vsgXchange_FOUND
options->add(vsgXchange::all::create());
#endif

arguments.read(options);

auto windowTraits = vsg::WindowTraits::create();
windowTraits->windowTitle = "vsgQt6 widgets";
windowTraits->debugLayer = arguments.read({"--debug", "-d"});
windowTraits->apiDumpLayer = arguments.read({"--api", "-a"});
arguments.read("--samples", windowTraits->samples);
arguments.read({"--window", "-w"}, windowTraits->width, windowTraits->height);
if (arguments.read({"--fullscreen", "--fs"})) windowTraits->fullscreen = true;

bool continuousUpdate = !arguments.read({"--event-driven", "--ed"});
auto interval = arguments.value<int>(8, "--interval");

if (arguments.errors())
return arguments.writeErrorMessages(std::cerr);

if (argc <= 1)
{
std::cout << "Please specify a 3d model or image file on the command line."
<< std::endl;
return 1;
}

vsg::Path filename = arguments[1];

auto vsg_scene = vsg::read_cast<vsg::Node>(filename, options);
if (!vsg_scene)
{
std::cout << "Failed to load a valid scene graph. Please specify a valid 3d "
"model or image file on the command line."
<< std::endl;
return 1;
}


QMainWindow* mainWindow = new QMainWindow();

// create the viewer that will manage all the rendering of the views
auto viewer = vsgQt6::Viewer::create();

auto widget = createWidget(viewer, windowTraits, vsg_scene, nullptr, "First Widget");

mainWindow->setCentralWidget(widget);

mainWindow->setGeometry(windowTraits->x, windowTraits->y, windowTraits->width, windowTraits->height);

mainWindow->show();

if (interval >= 0) viewer->setInterval(interval);
viewer->continuousUpdate = continuousUpdate;

viewer->addEventHandler(vsg::CloseHandler::create(viewer));
viewer->compile();

return application.exec();
}
25 changes: 14 additions & 11 deletions ext/glslang.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@

include(GNUInstallDirs)

set(GLSLANG_INSTALL_DIR "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/glslang")

# Set paths
if(APPLE)
set(SPIRV-Tools_DIR ${CMAKE_CURRENT_BINARY_DIR}/glslang-install/lib/cmake/SPIRV-Tools CACHE PATH "" FORCE)
set(SPIRV-Tools-opt_DIR ${CMAKE_CURRENT_BINARY_DIR}/glslang-install/lib/cmake/SPIRV-Tools-opt CACHE PATH "" FORCE)
set(SPIRV-Tools_DIR ${GLSLANG_INSTALL_DIR}/lib/cmake/SPIRV-Tools CACHE PATH "" FORCE)
set(SPIRV-Tools-opt_DIR ${GLSLANG_INSTALL_DIR}/lib/cmake/SPIRV-Tools-opt CACHE PATH "" FORCE)
else()
set(SPIRV-Tools_DIR ${CMAKE_CURRENT_BINARY_DIR}/glslang-install/SPIRV-Tools/cmake CACHE PATH "" FORCE)
set(SPIRV-Tools-opt_DIR ${CMAKE_CURRENT_BINARY_DIR}/glslang-install/SPIRV-Tools-opt/cmake CACHE PATH "" FORCE)
set(SPIRV-Tools_DIR ${GLSLANG_INSTALL_DIR}/SPIRV-Tools/cmake CACHE PATH "" FORCE)
set(SPIRV-Tools-opt_DIR ${GLSLANG_INSTALL_DIR}/SPIRV-Tools-opt/cmake CACHE PATH "" FORCE)
endif()
set(glslang_DIR ${CMAKE_CURRENT_BINARY_DIR}/glslang-install/${CMAKE_INSTALL_LIBDIR}/cmake/glslang CACHE PATH "" FORCE)

set(GLSLANG_REQUIRED_VERSION 14.3.0)

Expand All @@ -30,9 +31,11 @@ if(NOT ${glslang_FOUND})
GIT_REPOSITORY "https://github.com/KhronosGroup/glslang.git"
GIT_TAG "4a9f08891540263e19c2468dd602ffdd8c446390" # Use this commit (version 14.3) until VSG updates against glslang 15
GIT_PROGRESS TRUE
FIND_PACKAGE_ARGS
)
FetchContent_MakeAvailable(glslang)
FetchContent_GetProperties(glslang)
if (NOT glslang_POPULATED)
FetchContent_Populate(glslang)
endif()
message("glslang_SOURCE_DIR=${glslang_SOURCE_DIR}")
message("glslang_BINARY_DIR=${glslang_BINARY_DIR}")

Expand All @@ -54,17 +57,17 @@ if(NOT ${glslang_FOUND})
COMMAND ${CMAKE_COMMAND} --build . -j16 --config ${CMAKE_BUILD_TYPE}
WORKING_DIRECTORY ${glslang_BINARY_DIR}
)
message("[glslang-wrapper message]: Installing glslang to ${CMAKE_CURRENT_BINARY_DIR}/glslang-install")

message("[glslang-wrapper message]: Installing glslang to ${GLSLANG_INSTALL_DIR}")
# Install it
execute_process(
COMMAND ${CMAKE_COMMAND} --install . --prefix ${CMAKE_CURRENT_BINARY_DIR}/glslang-install --config ${CMAKE_BUILD_TYPE}
COMMAND ${CMAKE_COMMAND} --install . --prefix ${GLSLANG_INSTALL_DIR} --config ${CMAKE_BUILD_TYPE}
WORKING_DIRECTORY ${glslang_BINARY_DIR}
)

set(glslang_DIR ${CMAKE_CURRENT_BINARY_DIR}/glslang-install/${CMAKE_INSTALL_LIBDIR}/cmake/glslang CACHE PATH "" FORCE)
find_package(glslang ${GLSLANG_REQUIRED_VERSION})
else()
message("[glslang-wrapper message]: glslang found. If you have reason to think that it might be out-of-date, delete ${CMAKE_CURRENT_BINARY_DIR}/glslang-install and re-run cmake.")
message("[glslang-wrapper message]: glslang found. If you have reason to think that it might be out-of-date, delete ${GLSLANG_INSTALL_DIR} and re-run cmake.")
endif()

message("* glslang_DIR=${glslang_DIR}")
76 changes: 76 additions & 0 deletions include/vsgQt6/Widget.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#pragma once

/* <editor-fold desc="MIT License">
Copyright(c) 2024 Nolan Kramer
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</editor-fold> */

#include <vsg/all.h>

#include <vsgQt6/Export.h>
#include <vsgQt6/KeyboardMap.h>
#include <vsgQt6/Viewer.h>
#include <vsgQt6/Window_QQuickWindow.h>

#include <QQuickWidget>

namespace vsgQt6
{

// Manages VSG integration with QQuickWidget. Throws when QQuickWindow::graphicsApi() != SGRendererInterface::Vulkan.
// This class works by hooking into QQuickWidget's QQuickWindow offscreen window and grabbing the surface.
// VSG then renders into the surface.
// QQuickWidget::quickWindow() -> QQuickWindow::beforeRendering() -> VSG render -> QQuickWindow::afterRendering()
class VSGQT6_DECLSPEC Widget : public QQuickWidget
{
Q_OBJECT
public:
Widget(vsg::ref_ptr<Viewer> in_viewer, QWidget* parent = nullptr);
~Widget();

vsg::ref_ptr<Viewer> viewer;
vsg::ref_ptr<Window_QQuickWindow> windowAdapter;

vsg::ref_ptr<vsg::WindowTraits> traits;
vsg::ref_ptr<KeyboardMap> keyboardMap;

operator vsg::ref_ptr<vsg::Window>() { return windowAdapter; }

/// Initialize the Vulkan integration using VulkanSceneGraph VkInstance/VkSurface support
virtual void initialize();

protected:
void cleanup();

bool event(QEvent* e) override;

void hideEvent(QHideEvent* ev) override;

void keyPressEvent(QKeyEvent*) override;
void keyReleaseEvent(QKeyEvent*) override;
void mouseMoveEvent(QMouseEvent*) override;
void mousePressEvent(QMouseEvent*) override;
void mouseReleaseEvent(QMouseEvent*) override;
void resizeEvent(QResizeEvent*) override;
void wheelEvent(QWheelEvent*) override;

/// convert Qt's window coordinate into Vulkan/VSG ones by scaling by the devicePixelRatio()
template<typename T>
int32_t convert_coord(T c) const { return static_cast<int32_t>(std::round(static_cast<qreal>(c) * devicePixelRatio())); }

std::pair<vsg::ButtonMask, uint32_t> convertMouseButtons(QMouseEvent* e) const;
std::pair<int32_t, int32_t> convertMousePosition(QMouseEvent* e) const;

private:
bool _initialized = false;
};
}

EVSG_type_name(vsgQt6::Widget);
42 changes: 42 additions & 0 deletions include/vsgQt6/Window_QQuickWindow.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#pragma once

/* <editor-fold desc="MIT License">
Copyright(c) 2024 Nolan Kramer
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
</editor-fold> */

#include <vsgQt6/Export.h>

#include <vsg/app/Window.h>

#include <QQuickWindow>

namespace vsgQt6
{

// Uses QVulkanInstance::surfaceForWindow() to retrieve the surface.
class VSGQT6_DECLSPEC Window_QQuickWindow : public vsg::Inherit<vsg::Window, Window_QQuickWindow>
{
public:
Window_QQuickWindow(QQuickWindow* quickWindow, vsg::ref_ptr<vsg::WindowTraits> in_traits);
~Window_QQuickWindow();

virtual const char* instanceExtensionSurfaceName() const override { return "VK_KHR_QQuickWindow_surface"; }

QQuickWindow* getQuickWindow() { return _quickWindow; }

protected:
virtual void _initSurface() override;

private:
QQuickWindow* _quickWindow;
};

}
7 changes: 7 additions & 0 deletions src/vsgQt6/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,16 @@ set(SOURCES
KeyboardMap.cpp
Window.cpp
Viewer.cpp
Window_QQuickWindow.cpp
Widget.cpp
)

set(HEADERS
../../include/vsgQt6/KeyboardMap.h
../../include/vsgQt6/Window.h
../../include/vsgQt6/Viewer.h
../../include/vsgQt6/Window_QQuickWindow.h
../../include/vsgQt6/Widget.h
)

# shared mode is automatically chosen by setting BUILD_SHARED_LIBS=ON
Expand All @@ -68,7 +72,10 @@ target_include_directories(vsgQt6
)
target_link_libraries(vsgQt6
PUBLIC
${QT_PACKAGE_NAME}::Gui
${QT_PACKAGE_NAME}::Widgets
${QT_PACKAGE_NAME}::Quick
${QT_PACKAGE_NAME}::QuickWidgets
vsg::vsg
)

Expand Down
Loading

0 comments on commit 1d3af3b

Please sign in to comment.