mirror of
https://github.com/lubosz/overte.git
synced 2025-04-27 09:55:45 +02:00
Merge branch 'serverless-domains' of github.com:sethalves/hifi into serverless-domains
This commit is contained in:
commit
f5a954d08e
211 changed files with 4422 additions and 8649 deletions
CMakeLists.txtINSTALL.md
assignment-client/src
cmake
externals
macros
templates
domain-server/src
interface
CMakeLists.txt
resources
src
libraries
animation/src
audio/src
baking/src
display-plugins/src/display-plugins
entities-renderer/src
RenderableMaterialEntityItem.cppRenderableMaterialEntityItem.hRenderableShapeEntityItem.cppRenderableShapeEntityItem.hRenderableTextEntityItem.cppRenderableWebEntityItem.cppRenderableWebEntityItem.h
entities/src
fbx/src
gl/src/gl
gpu-gl-common
CMakeLists.txt
src/gpu/gl
GLBackend.cppGLBackend.hGLBackendInput.cppGLBackendOutput.cppGLBackendPipeline.cppGLBackendQuery.cppGLBackendShader.cppGLBackendState.cppGLBackendTexture.cppGLBackendTransform.cppGLBuffer.cppGLBuffer.hGLFramebuffer.cppGLFramebuffer.hGLInputFormat.cppGLInputFormat.hGLPipeline.cppGLPipeline.hGLQuery.hGLShader.cppGLShader.hGLShared.cppGLShared.hGLState.cppGLState.hGLTexelFormat.cppGLTexelFormat.hGLTexture.cppGLTexture.h
gpu-gl
114
CMakeLists.txt
114
CMakeLists.txt
|
@ -1,4 +1,4 @@
|
|||
# If we're running under the gradle build, HIFI_ANDROID will be set here, but
|
||||
# If we're running under the gradle build, HIFI_ANDROID will be set here, but
|
||||
# ANDROID will not be set until after the `project` statement. This is the *ONLY*
|
||||
# place you need to use `HIFI_ANDROID` instead of `ANDROID`
|
||||
if (WIN32 AND NOT HIFI_ANDROID)
|
||||
|
@ -14,8 +14,12 @@ include("cmake/init.cmake")
|
|||
include("cmake/compiler.cmake")
|
||||
|
||||
if (BUILD_SCRIBE_ONLY)
|
||||
add_subdirectory(tools/scribe)
|
||||
return()
|
||||
add_subdirectory(tools/scribe)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED CLIENT_ONLY)
|
||||
set(CLIENT_ONLY 0)
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED SERVER_ONLY)
|
||||
|
@ -23,61 +27,67 @@ if (NOT DEFINED SERVER_ONLY)
|
|||
endif()
|
||||
|
||||
if (ANDROID OR UWP)
|
||||
set(MOBILE 1)
|
||||
set(MOBILE 1)
|
||||
else()
|
||||
set(MOBILE 0)
|
||||
set(MOBILE 0)
|
||||
endif()
|
||||
|
||||
set(BUILD_CLIENT_OPTION ON)
|
||||
set(BUILD_SERVER_OPTION ON)
|
||||
set(BUILD_TESTS_OPTION ON)
|
||||
set(BUILD_TOOLS_OPTION ON)
|
||||
set(BUILD_INSTALLER_OPTION ON)
|
||||
set(GLES_OPTION OFF)
|
||||
set(DISABLE_QML_OPTION OFF)
|
||||
|
||||
if (ANDROID OR UWP)
|
||||
option(BUILD_SERVER "Build server components" OFF)
|
||||
option(BUILD_TOOLS "Build tools" OFF)
|
||||
option(BUILD_INSTALLER "Build installer" OFF)
|
||||
else()
|
||||
option(BUILD_SERVER "Build server components" ON)
|
||||
option(BUILD_TOOLS "Build tools" ON)
|
||||
option(BUILD_INSTALLER "Build installer" ON)
|
||||
set(BUILD_SERVER_OPTION OFF)
|
||||
set(BUILD_TOOLS_OPTION OFF)
|
||||
set(BUILD_INSTALLER OFF)
|
||||
endif()
|
||||
|
||||
if (CLIENT_ONLY)
|
||||
set(BUILD_SERVER_OPTION OFF)
|
||||
endif()
|
||||
|
||||
if (SERVER_ONLY)
|
||||
option(BUILD_CLIENT "Build client components" OFF)
|
||||
option(BUILD_TESTS "Build tests" OFF)
|
||||
else()
|
||||
option(BUILD_CLIENT "Build client components" ON)
|
||||
option(BUILD_TESTS "Build tests" ON)
|
||||
set(BUILD_CLIENT_OPTION OFF)
|
||||
set(BUILD_TESTS_OPTION OFF)
|
||||
endif()
|
||||
|
||||
if (ANDROID)
|
||||
option(USE_GLES "Use OpenGL ES" ON)
|
||||
set(PLATFORM_QT_COMPONENTS AndroidExtras WebView)
|
||||
set(GLES_OPTION ON)
|
||||
set(PLATFORM_QT_COMPONENTS AndroidExtras WebView)
|
||||
else ()
|
||||
option(USE_GLES "Use OpenGL ES" OFF)
|
||||
set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets)
|
||||
set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets)
|
||||
endif ()
|
||||
|
||||
if (USE_GLES AND (NOT ANDROID))
|
||||
option(DISABLE_QML "Disable QML" ON)
|
||||
else()
|
||||
option(DISABLE_QML "Disable QML" OFF)
|
||||
set(DISABLE_QML_OPTION ON)
|
||||
endif()
|
||||
|
||||
option(BUILD_CLIENT "Build client components" ${BUILD_CLIENT_OPTION})
|
||||
option(BUILD_SERVER "Build server components" ${BUILD_SERVER_OPTION})
|
||||
option(BUILD_TESTS "Build tests" ${BUILD_TESTS_OPTION})
|
||||
option(BUILD_TOOLS "Build tools" ${BUILD_TOOLS_OPTION})
|
||||
option(BUILD_INSTALLER "Build installer" ${BUILD_INSTALLER_OPTION})
|
||||
option(USE_GLES "Use OpenGL ES" ${GLES_OPTION})
|
||||
option(DISABLE_QML "Disable QML" ${DISABLE_QML_OPTION})
|
||||
option(DISABLE_KTX_CACHE "Disable KTX Cache" OFF)
|
||||
|
||||
|
||||
|
||||
set(PLATFORM_QT_GL OpenGL)
|
||||
|
||||
if (USE_GLES)
|
||||
add_definitions(-DUSE_GLES)
|
||||
set(PLATFORM_GL_BACKEND gpu-gles)
|
||||
set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gles)
|
||||
else()
|
||||
set(PLATFORM_GL_BACKEND gpu-gl)
|
||||
set(PLATFORM_GL_BACKEND gpu-gl-common gpu-gl)
|
||||
endif()
|
||||
|
||||
|
||||
foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS})
|
||||
list(APPEND PLATFORM_QT_LIBRARIES "Qt5::${PLATFORM_QT_COMPONENT}")
|
||||
endforeach()
|
||||
|
||||
|
||||
MESSAGE(STATUS "Build server: " ${BUILD_SERVER})
|
||||
MESSAGE(STATUS "Build client: " ${BUILD_CLIENT})
|
||||
MESSAGE(STATUS "Build tests: " ${BUILD_TESTS})
|
||||
|
@ -86,17 +96,17 @@ MESSAGE(STATUS "Build installer: " ${BUILD_INSTALLER})
|
|||
MESSAGE(STATUS "GL ES: " ${USE_GLES})
|
||||
|
||||
if (DISABLE_QML)
|
||||
MESSAGE(STATUS "QML disabled!")
|
||||
add_definitions(-DDISABLE_QML)
|
||||
MESSAGE(STATUS "QML disabled!")
|
||||
add_definitions(-DDISABLE_QML)
|
||||
endif()
|
||||
|
||||
if (DISABLE_KTX_CACHE)
|
||||
MESSAGE(STATUS "KTX cache disabled!")
|
||||
add_definitions(-DDISABLE_KTX_CACHE)
|
||||
MESSAGE(STATUS "KTX cache disabled!")
|
||||
add_definitions(-DDISABLE_KTX_CACHE)
|
||||
endif()
|
||||
|
||||
if (UNIX AND DEFINED ENV{HIFI_MEMORY_DEBUGGING})
|
||||
MESSAGE(STATUS "Memory debugging is enabled")
|
||||
MESSAGE(STATUS "Memory debugging is enabled")
|
||||
endif()
|
||||
|
||||
#
|
||||
|
@ -132,8 +142,8 @@ set_packaging_parameters()
|
|||
|
||||
# FIXME hack to work on the proper Android toolchain
|
||||
if (ANDROID)
|
||||
add_subdirectory(android/app)
|
||||
return()
|
||||
add_subdirectory(android/app)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# add subdirectories for all targets
|
||||
|
@ -148,32 +158,30 @@ if (BUILD_SERVER)
|
|||
endif()
|
||||
|
||||
if (BUILD_CLIENT)
|
||||
add_subdirectory(interface)
|
||||
set_target_properties(interface PROPERTIES FOLDER "Apps")
|
||||
if (ANDROID)
|
||||
add_subdirectory(gvr-interface)
|
||||
set_target_properties(gvr-interface PROPERTIES FOLDER "Apps")
|
||||
endif()
|
||||
add_subdirectory(interface)
|
||||
set_target_properties(interface PROPERTIES FOLDER "Apps")
|
||||
|
||||
option(USE_SIXENSE "Build Interface with sixense library/plugin" OFF)
|
||||
endif()
|
||||
|
||||
if (BUILD_CLIENT OR BUILD_SERVER)
|
||||
add_subdirectory(plugins)
|
||||
add_subdirectory(plugins)
|
||||
endif()
|
||||
|
||||
# BUILD_TOOLS option will be handled inside the tools's CMakeLists.txt because 'scribe' tool is required for build anyway
|
||||
add_subdirectory(tools)
|
||||
|
||||
if (BUILD_TESTS)
|
||||
add_subdirectory(tests)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
if (BUILD_INSTALLER)
|
||||
if (UNIX)
|
||||
install(
|
||||
DIRECTORY "${CMAKE_SOURCE_DIR}/scripts"
|
||||
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/interface
|
||||
COMPONENT ${CLIENT_COMPONENT}
|
||||
)
|
||||
endif()
|
||||
generate_installers()
|
||||
if (UNIX)
|
||||
install(
|
||||
DIRECTORY "${CMAKE_SOURCE_DIR}/scripts"
|
||||
DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/interface
|
||||
COMPONENT ${CLIENT_COMPONENT}
|
||||
)
|
||||
endif()
|
||||
generate_installers()
|
||||
endif()
|
||||
|
|
|
@ -17,6 +17,8 @@ To produce an executable installer on Windows, the following are required:
|
|||
- [Nullsoft Scriptable Install System](http://nsis.sourceforge.net/Download) - 3.0b3
|
||||
- [UAC Plug-in for Nullsoft](http://nsis.sourceforge.net/UAC_plug-in) - 0.2.4c
|
||||
- [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6
|
||||
- [Inetc Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0
|
||||
- [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0
|
||||
|
||||
Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System.
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
|||
ThreadedAssignment(message)
|
||||
{
|
||||
// make sure we hear about node kills so we can tell the other nodes
|
||||
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &AvatarMixer::nodeKilled);
|
||||
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled);
|
||||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
packetReceiver.registerListener(PacketType::AvatarData, this, "queueIncomingPacket");
|
||||
|
@ -423,14 +423,15 @@ void AvatarMixer::throttle(std::chrono::microseconds duration, int frame) {
|
|||
}
|
||||
}
|
||||
|
||||
void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
|
||||
if (killedNode->getType() == NodeType::Agent
|
||||
&& killedNode->getLinkedData()) {
|
||||
|
||||
void AvatarMixer::handleAvatarKilled(SharedNodePointer avatarNode) {
|
||||
if (avatarNode->getType() == NodeType::Agent
|
||||
&& avatarNode->getLinkedData()) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
{ // decrement sessionDisplayNames table and possibly remove
|
||||
QMutexLocker nodeDataLocker(&killedNode->getLinkedData()->getMutex());
|
||||
AvatarMixerClientData* nodeData = dynamic_cast<AvatarMixerClientData*>(killedNode->getLinkedData());
|
||||
QMutexLocker nodeDataLocker(&avatarNode->getLinkedData()->getMutex());
|
||||
AvatarMixerClientData* nodeData = dynamic_cast<AvatarMixerClientData*>(avatarNode->getLinkedData());
|
||||
const QString& baseDisplayName = nodeData->getBaseDisplayName();
|
||||
// No sense guarding against very rare case of a node with no entry, as this will work without the guard and do one less lookup in the common case.
|
||||
if (--_sessionDisplayNames[baseDisplayName].second <= 0) {
|
||||
|
@ -447,12 +448,12 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
|
|||
// we relay avatar kill packets to agents that are not upstream
|
||||
// and downstream avatar mixers, if the node that was just killed was being replicated
|
||||
return (node->getType() == NodeType::Agent && !node->isUpstream()) ||
|
||||
(killedNode->isReplicated() && shouldReplicateTo(*killedNode, *node));
|
||||
(avatarNode->isReplicated() && shouldReplicateTo(*avatarNode, *node));
|
||||
}, [&](const SharedNodePointer& node) {
|
||||
if (node->getType() == NodeType::Agent) {
|
||||
if (!killPacket) {
|
||||
killPacket = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason));
|
||||
killPacket->write(killedNode->getUUID().toRfc4122());
|
||||
killPacket->write(avatarNode->getUUID().toRfc4122());
|
||||
killPacket->writePrimitive(KillAvatarReason::AvatarDisconnected);
|
||||
}
|
||||
|
||||
|
@ -462,7 +463,7 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
|
|||
if (!replicatedKillPacket) {
|
||||
replicatedKillPacket = NLPacket::create(PacketType::ReplicatedKillAvatar,
|
||||
NUM_BYTES_RFC4122_UUID + sizeof(KillAvatarReason));
|
||||
replicatedKillPacket->write(killedNode->getUUID().toRfc4122());
|
||||
replicatedKillPacket->write(avatarNode->getUUID().toRfc4122());
|
||||
replicatedKillPacket->writePrimitive(KillAvatarReason::AvatarDisconnected);
|
||||
}
|
||||
|
||||
|
@ -479,7 +480,7 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (node->getUUID() == killedNode->getUUID()) {
|
||||
if (node->getUUID() == avatarNode->getUUID()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -489,7 +490,7 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
|
|||
QMetaObject::invokeMethod(node->getLinkedData(),
|
||||
"cleanupKilledNode",
|
||||
Qt::AutoConnection,
|
||||
Q_ARG(const QUuid&, QUuid(killedNode->getUUID())));
|
||||
Q_ARG(const QUuid&, QUuid(avatarNode->getUUID())));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -605,7 +606,9 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes
|
|||
|
||||
void AvatarMixer::handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node) {
|
||||
auto start = usecTimestampNow();
|
||||
DependencyManager::get<NodeList>()->processKillNode(*message);
|
||||
handleAvatarKilled(node);
|
||||
|
||||
node->setLinkedData(nullptr);
|
||||
auto end = usecTimestampNow();
|
||||
_handleKillAvatarPacketElapsedTime += (end - start);
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ public slots:
|
|||
/// runs the avatar mixer
|
||||
void run() override;
|
||||
|
||||
void nodeKilled(SharedNodePointer killedNode);
|
||||
void handleAvatarKilled(SharedNodePointer killedNode);
|
||||
|
||||
void sendStatsPacket() override;
|
||||
|
||||
|
|
|
@ -442,12 +442,16 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream
|
|||
PrioritizedEntity queuedItem = _sendQueue.top();
|
||||
EntityItemPointer entity = queuedItem.getEntity();
|
||||
if (entity) {
|
||||
// Only send entities that match the jsonFilters, but keep track of everything we've tried to send so we don't try to send it again
|
||||
const QUuid& entityID = entity->getID();
|
||||
// Only send entities that match the jsonFilters, but keep track of everything we've tried to send so we don't try to send it again;
|
||||
// also send if we previously matched since this represents change to a matched item.
|
||||
bool entityMatchesFilters = entity->matchesJSONFilters(jsonFilters);
|
||||
if (entityMatchesFilters || entityNodeData->isEntityFlaggedAsExtra(entity->getID())) {
|
||||
bool entityPreviouslyMatchedFilter = entityNodeData->sentFilteredEntity(entityID);
|
||||
|
||||
if (entityMatchesFilters || entityNodeData->isEntityFlaggedAsExtra(entityID) || entityPreviouslyMatchedFilter) {
|
||||
if (!jsonFilters.isEmpty() && entityMatchesFilters) {
|
||||
// Record explicitly filtered-in entity so that extra entities can be flagged.
|
||||
entityNodeData->insertSentFilteredEntity(entity->getID());
|
||||
entityNodeData->insertSentFilteredEntity(entityID);
|
||||
}
|
||||
OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData);
|
||||
|
||||
|
@ -458,6 +462,10 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream
|
|||
params.stopReason = EncodeBitstreamParams::DIDNT_FIT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (entityPreviouslyMatchedFilter && !entityMatchesFilters) {
|
||||
entityNodeData->removeSentFilteredEntity(entityID);
|
||||
}
|
||||
++_numEntities;
|
||||
}
|
||||
if (queuedItem.shouldForceRemove()) {
|
||||
|
|
|
@ -476,6 +476,7 @@ void EntityScriptServer::clear() {
|
|||
// do this here (instead of in deleter) to avoid marshalling unload signals back to this thread
|
||||
_entitiesScriptEngine->unloadAllEntityScripts();
|
||||
_entitiesScriptEngine->stop();
|
||||
_entitiesScriptEngine->waitTillDoneRunning();
|
||||
}
|
||||
|
||||
_entityViewer.clear();
|
||||
|
@ -565,8 +566,15 @@ void EntityScriptServer::handleOctreePacket(QSharedPointer<ReceivedMessage> mess
|
|||
void EntityScriptServer::aboutToFinish() {
|
||||
shutdownScriptEngine();
|
||||
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
// our entity tree is going to go away so tell that to the EntityScriptingInterface
|
||||
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(nullptr);
|
||||
entityScriptingInterface->setEntityTree(nullptr);
|
||||
|
||||
// Should always be true as they are singletons.
|
||||
if (entityScriptingInterface->getPacketSender() == &_entityEditSender) {
|
||||
// The packet sender is about to go away.
|
||||
entityScriptingInterface->setPacketSender(nullptr);
|
||||
}
|
||||
|
||||
DependencyManager::get<ResourceManager>()->cleanup();
|
||||
|
||||
|
|
4
cmake/externals/nvtt/CMakeLists.txt
vendored
4
cmake/externals/nvtt/CMakeLists.txt
vendored
|
@ -29,8 +29,8 @@ else ()
|
|||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/nvidia-texture-tools-2.1.0.hifi.zip
|
||||
URL_MD5 5794b950f8b265a9a41b2839b3bf7ebb
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/nvidia-texture-tools-2.1.0.hifi-83462e4.zip
|
||||
URL_MD5 602776e08515b54bfa1b8dc455003f0f
|
||||
CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DNVTT_SHARED=1 -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_POSITION_INDEPENDENT_CODE=ON
|
||||
LOG_DOWNLOAD 1
|
||||
LOG_CONFIGURE 1
|
||||
|
|
2
cmake/externals/quazip/CMakeLists.txt
vendored
2
cmake/externals/quazip/CMakeLists.txt
vendored
|
@ -4,7 +4,7 @@ cmake_policy(SET CMP0046 OLD)
|
|||
|
||||
include(ExternalProject)
|
||||
|
||||
set(QUAZIP_CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_PREFIX_PATH=${QT_CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_NAME_DIR:PATH=<INSTALL_DIR>/lib -DZLIB_ROOT=${ZLIB_ROOT} -DCMAKE_POSITION_INDEPENDENT_CODE=ON)
|
||||
set(QUAZIP_CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_PREFIX_PATH=${QT_CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_NAME_DIR:PATH=<INSTALL_DIR>/lib -DZLIB_ROOT=${ZLIB_ROOT} -DCMAKE_POSITION_INDEPENDENT_CODE=ON)
|
||||
|
||||
if (APPLE)
|
||||
else ()
|
||||
|
|
|
@ -23,7 +23,7 @@ macro(GENERATE_INSTALLERS)
|
|||
set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME})
|
||||
set(CPACK_NSIS_PACKAGE_NAME ${_DISPLAY_NAME})
|
||||
if (PR_BUILD)
|
||||
set(CPACK_NSIS_COMPRESSOR "/SOLID bzip2")
|
||||
set(CPACK_NSIS_COMPRESSOR "bzip2")
|
||||
endif ()
|
||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY ${_DISPLAY_NAME})
|
||||
|
||||
|
@ -46,9 +46,35 @@ macro(GENERATE_INSTALLERS)
|
|||
set(UNINSTALLER_HEADER_IMAGE "")
|
||||
fix_path_for_nsis(${_UNINSTALLER_HEADER_BAD_PATH} UNINSTALLER_HEADER_IMAGE)
|
||||
|
||||
# grab the latest VC redist (2017) and add it to the installer, our NSIS template
|
||||
# will call it during the install
|
||||
install(CODE "file(DOWNLOAD https://go.microsoft.com/fwlink/?LinkId=746572 \"\${CMAKE_INSTALL_PREFIX}/vcredist_x64.exe\")")
|
||||
# we use external libraries that still need the 120 (VS2013) redistributables
|
||||
# so we include them as well until those external libraries are updated
|
||||
# to use the redistributables that match what we build our applications for
|
||||
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS
|
||||
"C:/Windows/System32/msvcp120.dll"
|
||||
"C:/Windows/System32/msvcr120.dll"
|
||||
)
|
||||
|
||||
set(CMAKE_INSTALL_UCRT_LIBRARIES TRUE)
|
||||
set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION ${INTERFACE_INSTALL_DIR})
|
||||
set(CMAKE_INSTALL_SYSTEM_RUNTIME_COMPONENT ${CLIENT_COMPONENT})
|
||||
include(InstallRequiredSystemLibraries)
|
||||
|
||||
if (CLIENT_ONLY OR SERVER_ONLY)
|
||||
set(CPACK_MONOLITHIC_INSTALL 1)
|
||||
endif ()
|
||||
|
||||
# setup conditional checks for server component selection depending on
|
||||
# the inclusion of the server component at all
|
||||
if (CLIENT_ONLY)
|
||||
set(SERVER_COMPONENT_CONDITIONAL "0 == 1")
|
||||
set(CLIENT_COMPONENT_CONDITIONAL "1 == 1")
|
||||
elseif (SERVER_ONLY)
|
||||
set(SERVER_COMPONENT_CONDITIONAL "1 == 1")
|
||||
set(CLIENT_COMPONENT_CONDITIONAL "0 == 1")
|
||||
else ()
|
||||
set(SERVER_COMPONENT_CONDITIONAL "\\\${SectionIsSelected} \\\${${SERVER_COMPONENT}}")
|
||||
set(CLIENT_COMPONENT_CONDITIONAL "\\\${SectionIsSelected} \\\${${CLIENT_COMPONENT}}")
|
||||
endif ()
|
||||
elseif (APPLE)
|
||||
# produce a drag and drop DMG on OS X
|
||||
set(CPACK_GENERATOR "DragNDrop")
|
||||
|
@ -79,8 +105,13 @@ macro(GENERATE_INSTALLERS)
|
|||
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE")
|
||||
|
||||
cpack_add_component(${CLIENT_COMPONENT} DISPLAY_NAME "High Fidelity Interface")
|
||||
cpack_add_component(${SERVER_COMPONENT} DISPLAY_NAME "High Fidelity Sandbox")
|
||||
if (BUILD_CLIENT)
|
||||
cpack_add_component(${CLIENT_COMPONENT} DISPLAY_NAME "High Fidelity Interface")
|
||||
endif ()
|
||||
|
||||
if (BUILD_SERVER)
|
||||
cpack_add_component(${SERVER_COMPONENT} DISPLAY_NAME "High Fidelity Sandbox")
|
||||
endif ()
|
||||
|
||||
include(CPack)
|
||||
endmacro()
|
||||
|
|
|
@ -39,7 +39,9 @@ macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT)
|
|||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> \"$<TARGET_FILE:${TARGET_NAME}>\""
|
||||
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND}\
|
||||
${EXTRA_DEPLOY_OPTIONS} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release>\
|
||||
--no-compiler-runtime --no-opengl-sw --no-angle -no-system-d3d-compiler \"$<TARGET_FILE:${TARGET_NAME}>\""
|
||||
)
|
||||
|
||||
set(QTAUDIO_PATH "$<TARGET_FILE_DIR:${TARGET_NAME}>/audio")
|
||||
|
|
|
@ -27,6 +27,11 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
message(STATUS "The BRANCH environment variable is: $ENV{BRANCH}")
|
||||
message(STATUS "The RELEASE_TYPE variable is: ${RELEASE_TYPE}")
|
||||
|
||||
# setup component categories for installer
|
||||
set(DDE_COMPONENT dde)
|
||||
set(CLIENT_COMPONENT client)
|
||||
set(SERVER_COMPONENT server)
|
||||
|
||||
if (RELEASE_TYPE STREQUAL "PRODUCTION")
|
||||
set(DEPLOY_PACKAGE TRUE)
|
||||
set(PRODUCTION_BUILD 1)
|
||||
|
@ -149,13 +154,10 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
set(CLIENT_LAUNCH_NOW_REG_KEY "ClientLaunchAfterInstall")
|
||||
set(SERVER_LAUNCH_NOW_REG_KEY "ServerLaunchAfterInstall")
|
||||
set(CUSTOM_INSTALL_REG_KEY "CustomInstall")
|
||||
set(CLIENT_ID_REG_KEY "ClientGUID")
|
||||
set(GA_TRACKING_ID $ENV{GA_TRACKING_ID})
|
||||
endif ()
|
||||
|
||||
# setup component categories for installer
|
||||
set(DDE_COMPONENT dde)
|
||||
set(CLIENT_COMPONENT client)
|
||||
set(SERVER_COMPONENT server)
|
||||
|
||||
# print out some results for testing this new build feature
|
||||
message(STATUS "The BUILD_GLOBAL_SERVICES variable is: ${BUILD_GLOBAL_SERVICES}")
|
||||
message(STATUS "The USE_STABLE_GLOBAL_SERVICES variable is: ${USE_STABLE_GLOBAL_SERVICES}")
|
||||
|
|
|
@ -41,6 +41,10 @@ set(CONSOLE_STARTUP_REG_KEY "@CONSOLE_STARTUP_REG_KEY@")
|
|||
set(SERVER_LAUNCH_NOW_REG_KEY "@SERVER_LAUNCH_NOW_REG_KEY@")
|
||||
set(CLIENT_LAUNCH_NOW_REG_KEY "@CLIENT_LAUNCH_NOW_REG_KEY@")
|
||||
set(CUSTOM_INSTALL_REG_KEY "@CUSTOM_INSTALL_REG_KEY@")
|
||||
set(GA_TRACKING_ID "@GA_TRACKING_ID@")
|
||||
set(CLIENT_ID_REG_KEY "@CLIENT_ID_REG_KEY@")
|
||||
set(INSTALLER_HEADER_IMAGE "@INSTALLER_HEADER_IMAGE@")
|
||||
set(UNINSTALLER_HEADER_IMAGE "@UNINSTALLER_HEADER_IMAGE@")
|
||||
set(ADD_REMOVE_ICON_PATH "@ADD_REMOVE_ICON_PATH@")
|
||||
set(SERVER_COMPONENT_CONDITIONAL "@SERVER_COMPONENT_CONDITIONAL@")
|
||||
set(CLIENT_COMPONENT_CONDITIONAL "@CLIENT_COMPONENT_CONDITIONAL@")
|
||||
|
|
|
@ -319,6 +319,78 @@ Function DownloadFile
|
|||
FunctionEnd
|
||||
!endif
|
||||
|
||||
|
||||
!include NSISpcre.nsh
|
||||
!insertmacro REMatches
|
||||
|
||||
Var CampaignName
|
||||
|
||||
!macro GetCampaignName RetVar
|
||||
Call GetCampaignName
|
||||
Pop ${RetVar}
|
||||
!macroend
|
||||
|
||||
Function GetCampaignName
|
||||
Push $0 ; Stash $0
|
||||
|
||||
; Parse filename out of the path
|
||||
${RECaptureMatches} $0 "([^\\]*\\)*(.*)\.exe" $EXEPATH 0
|
||||
${If} $0 == 2
|
||||
Pop $0 ; Discard Path
|
||||
Pop $0 ; Recover filename
|
||||
; Parse campaign out of the filename
|
||||
${RECaptureMatches} $0 "HighFidelity-([^-]*-)Beta-.*" $0 0
|
||||
${If} $0 == 1
|
||||
Pop $0 ; Recover campaign name
|
||||
StrCpy $0 $0 -1 0 ; Remove trailing - and copy to _RetVar
|
||||
${Else}
|
||||
StrCpy $0 ""
|
||||
${EndIf}
|
||||
${Else}
|
||||
StrCpy $0 ""
|
||||
${EndIf}
|
||||
|
||||
Exch $0 ; Restore $0 and push result
|
||||
FunctionEnd
|
||||
|
||||
!macro CreateGUID RetVar
|
||||
System::Call 'ole32::CoCreateGuid(g .s)'
|
||||
Pop ${RetVar}
|
||||
; Strip opening and closing braces
|
||||
StrCpy ${RetVar} ${RetVar} -1 1
|
||||
!macroend
|
||||
|
||||
Var GAClientID
|
||||
|
||||
!macro InitGAClientID
|
||||
; Generate a new GUID on every run for now
|
||||
!insertmacro CreateGUID $GAClientID
|
||||
!macroend
|
||||
|
||||
!macro GoogleAnalytics Category Action Label Value
|
||||
${If} "@GA_TRACKING_ID@" != ""
|
||||
Push $0
|
||||
Push $1
|
||||
|
||||
StrCpy $0 "https://google-analytics.com/collect?v=1&tid=@GA_TRACKING_ID@"
|
||||
StrCpy $0 "$0&cid=$GAClientID&t=event&ec=${Category}&ea=${Action}"
|
||||
|
||||
${If} "${Label}" != ""
|
||||
StrCpy $0 "$0&el=${Label}"
|
||||
${EndIf}
|
||||
${If} "${Value}" != ""
|
||||
StrCpy $0 "$0&ev=${Value}"
|
||||
${EndIf}
|
||||
|
||||
GetTempFileName $1
|
||||
inetc::get /SILENT $0 $1 /END
|
||||
Delete $1
|
||||
|
||||
Pop $1
|
||||
Pop $0
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
;--------------------------------
|
||||
; Installation types
|
||||
|
||||
|
@ -342,28 +414,38 @@ SectionEnd
|
|||
|
||||
;--------------------------------
|
||||
;Pages
|
||||
!define MUI_CUSTOMFUNCTION_ABORT OnUserAbort
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageWelcomePre
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageLicensePre
|
||||
!insertmacro MUI_PAGE_LICENSE "@CPACK_RESOURCE_FILE_LICENSE@"
|
||||
|
||||
Page custom InstallTypesPage ReadInstallTypes
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageDirectoryPre
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
|
||||
;Start Menu Folder Page Configuration
|
||||
!define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKLM"
|
||||
!define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@"
|
||||
!define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder"
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageStartMenuPre
|
||||
!insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageComponentsPre
|
||||
@CPACK_NSIS_PAGE_COMPONENTS@
|
||||
|
||||
; the MUI_PAGE_CUSTOMFUNCTION_PRE shouldn't be defined here
|
||||
; which can happen for a component-less (like client only) install
|
||||
!ifdef MUI_PAGE_CUSTOMFUNCTION_PRE
|
||||
!undef MUI_PAGE_CUSTOMFUNCTION_PRE
|
||||
!endif
|
||||
|
||||
Page custom PostInstallOptionsPage ReadPostInstallOptions
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE PageInstallFilesPre
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
|
@ -452,8 +534,40 @@ Var CopyFromProductionCheckbox
|
|||
Var ExpressInstallRadioButton
|
||||
Var CustomInstallRadioButton
|
||||
Var InstallTypeDialog
|
||||
Var Express
|
||||
Var CustomInstallTemporaryState
|
||||
Var Express
|
||||
|
||||
!macro MaybeSkipPage
|
||||
; Check if Express is set, if so, abort the post install options page
|
||||
${If} $Express == "1"
|
||||
Abort
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
Function OnUserAbort
|
||||
!insertmacro GoogleAnalytics "Installer" "Abort" "User Abort" ""
|
||||
FunctionEnd
|
||||
Function PageWelcomePre
|
||||
!insertmacro GoogleAnalytics "Installer" "Welcome" "" ""
|
||||
FunctionEnd
|
||||
Function PageLicensePre
|
||||
!insertmacro GoogleAnalytics "Installer" "License" "" ""
|
||||
FunctionEnd
|
||||
Function PageDirectoryPre
|
||||
!insertmacro MaybeSkipPage
|
||||
!insertmacro GoogleAnalytics "Installer" "Directory" "" ""
|
||||
FunctionEnd
|
||||
Function PageStartMenuPre
|
||||
!insertmacro MaybeSkipPage
|
||||
!insertmacro GoogleAnalytics "Installer" "StartMenu" "" ""
|
||||
FunctionEnd
|
||||
Function PageComponentsPre
|
||||
!insertmacro MaybeSkipPage
|
||||
!insertmacro GoogleAnalytics "Installer" "Components" "" ""
|
||||
FunctionEnd
|
||||
Function PageInstallFilesPre
|
||||
!insertmacro GoogleAnalytics "Installer" "Install" "" ""
|
||||
FunctionEnd
|
||||
|
||||
!macro SetInstallOption Checkbox OptionName Default
|
||||
; reads the value for the given install option to the registry
|
||||
|
@ -472,6 +586,8 @@ Var CustomInstallTemporaryState
|
|||
!macroend
|
||||
|
||||
Function InstallTypesPage
|
||||
!insertmacro GoogleAnalytics "Installer" "Install Types" "" ""
|
||||
|
||||
!insertmacro MUI_HEADER_TEXT "Choose Installation Type" "Express or Custom Install"
|
||||
|
||||
nsDialogs::Create 1018
|
||||
|
@ -502,10 +618,11 @@ Function InstallTypesPage
|
|||
|
||||
${If} $CustomInstallTemporaryState == ${BST_UNCHECKED}
|
||||
${NSD_Check} $ExpressInstallRadioButton
|
||||
Call ChangeExpressLabel
|
||||
${Else}
|
||||
Call ChangeCustomLabel
|
||||
${EndIf}
|
||||
|
||||
Call ChangeExpressLabel
|
||||
|
||||
nsDialogs::Show
|
||||
FunctionEnd
|
||||
|
||||
|
@ -523,14 +640,10 @@ Function ChangeCustomLabel
|
|||
Pop $R1
|
||||
FunctionEnd
|
||||
|
||||
Function AbortFunction
|
||||
; Check if Express is set, if so, abort the post install options page
|
||||
StrCmp $Express "1" 0 end
|
||||
Abort
|
||||
end:
|
||||
FunctionEnd
|
||||
|
||||
Function PostInstallOptionsPage
|
||||
!insertmacro MaybeSkipPage
|
||||
!insertmacro GoogleAnalytics "Installer" "Post Install Options" "" ""
|
||||
|
||||
!insertmacro MUI_HEADER_TEXT "Setup Options" ""
|
||||
|
||||
nsDialogs::Create 1018
|
||||
|
@ -540,15 +653,10 @@ Function PostInstallOptionsPage
|
|||
Abort
|
||||
${EndIf}
|
||||
|
||||
; Check if Express is set, if so, abort the post install options page
|
||||
StrCmp $Express "1" 0 end
|
||||
Abort
|
||||
end:
|
||||
|
||||
StrCpy $CurrentOffset 0
|
||||
StrCpy $OffsetUnits u
|
||||
|
||||
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
|
||||
${If} @CLIENT_COMPONENT_CONDITIONAL@
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @INTERFACE_HF_SHORTCUT_NAME@"
|
||||
Pop $DesktopClientCheckbox
|
||||
IntOp $CurrentOffset $CurrentOffset + 15
|
||||
|
@ -557,7 +665,7 @@ Function PostInstallOptionsPage
|
|||
!insertmacro SetInstallOption $DesktopClientCheckbox @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ ${BST_CHECKED}
|
||||
${EndIf}
|
||||
|
||||
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
|
||||
${If} @SERVER_COMPONENT_CONDITIONAL@
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @CONSOLE_HF_SHORTCUT_NAME@"
|
||||
Pop $DesktopServerCheckbox
|
||||
IntOp $CurrentOffset $CurrentOffset + 15
|
||||
|
@ -566,7 +674,7 @@ Function PostInstallOptionsPage
|
|||
!insertmacro SetInstallOption $DesktopServerCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED}
|
||||
${EndIf}
|
||||
|
||||
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
|
||||
${If} @SERVER_COMPONENT_CONDITIONAL@
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ after install"
|
||||
Pop $LaunchServerNowCheckbox
|
||||
|
||||
|
@ -580,7 +688,7 @@ Function PostInstallOptionsPage
|
|||
IntOp $CurrentOffset $CurrentOffset + 15
|
||||
${EndIf}
|
||||
|
||||
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
|
||||
${If} @CLIENT_COMPONENT_CONDITIONAL@
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @INTERFACE_HF_SHORTCUT_NAME@ after install"
|
||||
Pop $LaunchClientNowCheckbox
|
||||
IntOp $CurrentOffset $CurrentOffset + 30
|
||||
|
@ -593,7 +701,7 @@ Function PostInstallOptionsPage
|
|||
${EndIf}
|
||||
${EndIf}
|
||||
|
||||
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
|
||||
${If} @SERVER_COMPONENT_CONDITIONAL@
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ on startup"
|
||||
Pop $ServerStartupCheckbox
|
||||
IntOp $CurrentOffset $CurrentOffset + 15
|
||||
|
@ -602,7 +710,7 @@ Function PostInstallOptionsPage
|
|||
!insertmacro SetInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED}
|
||||
${EndIf}
|
||||
|
||||
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
|
||||
${If} @SERVER_COMPONENT_CONDITIONAL@
|
||||
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Perform a clean install (Delete older settings and content)"
|
||||
Pop $CleanInstallCheckbox
|
||||
IntOp $CurrentOffset $CurrentOffset + 15
|
||||
|
@ -610,11 +718,11 @@ Function PostInstallOptionsPage
|
|||
|
||||
${If} @PR_BUILD@ == 1
|
||||
; a PR build defaults all install options expect LaunchServerNowCheckbox, LaunchClientNowCheckbox and the settings copy to unchecked
|
||||
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
|
||||
${If} @CLIENT_COMPONENT_CONDITIONAL@
|
||||
${NSD_SetState} $DesktopClientCheckbox ${BST_UNCHECKED}
|
||||
${EndIf}
|
||||
|
||||
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
|
||||
${If} @SERVER_COMPONENT_CONDITIONAL@
|
||||
${NSD_SetState} $DesktopServerCheckbox ${BST_UNCHECKED}
|
||||
${NSD_SetState} $ServerStartupCheckbox ${BST_UNCHECKED}
|
||||
${EndIf}
|
||||
|
@ -673,12 +781,12 @@ Function ReadInstallTypes
|
|||
FunctionEnd
|
||||
|
||||
Function ReadPostInstallOptions
|
||||
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
|
||||
${If} @CLIENT_COMPONENT_CONDITIONAL@
|
||||
; check if the user asked for a desktop shortcut to High Fidelity
|
||||
${NSD_GetState} $DesktopClientCheckbox $DesktopClientState
|
||||
${EndIf}
|
||||
|
||||
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
|
||||
${If} @SERVER_COMPONENT_CONDITIONAL@
|
||||
; check if the user asked for a desktop shortcut to Sandbox
|
||||
${NSD_GetState} $DesktopServerCheckbox $DesktopServerState
|
||||
|
||||
|
@ -691,24 +799,24 @@ Function ReadPostInstallOptions
|
|||
${NSD_GetState} $CopyFromProductionCheckbox $CopyFromProductionState
|
||||
${EndIf}
|
||||
|
||||
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
|
||||
${If} @SERVER_COMPONENT_CONDITIONAL@
|
||||
; check if we need to launch the server post-install
|
||||
${NSD_GetState} $LaunchServerNowCheckbox $LaunchServerNowState
|
||||
${EndIf}
|
||||
|
||||
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
|
||||
${If} @CLIENT_COMPONENT_CONDITIONAL@
|
||||
; check if we need to launch the client post-install
|
||||
${NSD_GetState} $LaunchClientNowCheckbox $LaunchClientNowState
|
||||
${EndIf}
|
||||
|
||||
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
|
||||
${If} @CLIENT_COMPONENT_CONDITIONAL@
|
||||
; check if the user asked for a clean install
|
||||
${NSD_GetState} $CleanInstallCheckbox $CleanInstallState
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
Function HandlePostInstallOptions
|
||||
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
|
||||
${If} @CLIENT_COMPONENT_CONDITIONAL@
|
||||
; check if the user asked for a desktop shortcut to High Fidelity
|
||||
${If} $DesktopClientState == ${BST_CHECKED}
|
||||
CreateShortCut "$DESKTOP\@INTERFACE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"
|
||||
|
@ -719,7 +827,7 @@ Function HandlePostInstallOptions
|
|||
|
||||
${EndIf}
|
||||
|
||||
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
|
||||
${If} @SERVER_COMPONENT_CONDITIONAL@
|
||||
; check if the user asked for a desktop shortcut to Sandbox
|
||||
${If} $DesktopServerState == ${BST_CHECKED}
|
||||
CreateShortCut "$DESKTOP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"
|
||||
|
@ -748,7 +856,7 @@ Function HandlePostInstallOptions
|
|||
${EndIf}
|
||||
${EndIf}
|
||||
|
||||
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
|
||||
${If} @CLIENT_COMPONENT_CONDITIONAL@
|
||||
; check if the user asked for a clean install
|
||||
${If} $CleanInstallState == ${BST_CHECKED}
|
||||
SetShellVarContext current
|
||||
|
@ -785,7 +893,8 @@ Function HandlePostInstallOptions
|
|||
${EndIf}
|
||||
${EndIf}
|
||||
|
||||
${If} $LaunchServerNowState == ${BST_CHECKED}
|
||||
${If} @SERVER_COMPONENT_CONDITIONAL@
|
||||
${AndIf} $LaunchServerNowState == ${BST_CHECKED}
|
||||
!insertmacro WriteInstallOption @SERVER_LAUNCH_NOW_REG_KEY@ YES
|
||||
|
||||
; both launches use the explorer trick in case the user has elevated permissions for the installer
|
||||
|
@ -799,7 +908,7 @@ Function HandlePostInstallOptions
|
|||
Exec '"$WINDIR\explorer.exe" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"'
|
||||
${EndIf}
|
||||
|
||||
${Else}
|
||||
${ElseIf} @CLIENT_COMPONENT_CONDITIONAL@
|
||||
!insertmacro WriteInstallOption @SERVER_LAUNCH_NOW_REG_KEY@ NO
|
||||
|
||||
; launch uses the explorer trick in case the user has elevated permissions for the installer
|
||||
|
@ -837,9 +946,6 @@ Section "-Core installation"
|
|||
Delete "$INSTDIR\ui_resources_200_percent.pak"
|
||||
Delete "$INSTDIR\vccorlib120.dll"
|
||||
Delete "$INSTDIR\version"
|
||||
Delete "$INSTDIR\msvcr140.dll"
|
||||
Delete "$INSTDIR\msvcp140.dll"
|
||||
Delete "$INSTDIR\vcruntime140.dll"
|
||||
Delete "$INSTDIR\xinput1_3.dll"
|
||||
|
||||
; Delete old desktop shortcuts before they were renamed during Sandbox rename
|
||||
|
@ -858,11 +964,8 @@ Section "-Core installation"
|
|||
; Rename the incorrectly cased Raleway font
|
||||
Rename "$INSTDIR\resources\qml\styles-uit\RalewaySemibold.qml" "$INSTDIR\resources\qml\styles-uit\RalewaySemiBold.qml"
|
||||
|
||||
ExecWait "$INSTDIR\vcredist_x64.exe /install /q /norestart"
|
||||
|
||||
; Remove the Old Interface directory and vcredist_x64.exe (from installs prior to Server Console)
|
||||
RMDir /r "$INSTDIR\Interface"
|
||||
Delete "$INSTDIR\vcredist_x64.exe"
|
||||
|
||||
;Use the entire tree produced by the INSTALL target. Keep the
|
||||
;list of directories here in sync with the RMDir commands below.
|
||||
|
@ -931,7 +1034,7 @@ Section "-Core installation"
|
|||
@CPACK_NSIS_CREATE_ICONS_EXTRA@
|
||||
|
||||
; Conditional handling for Interface specific options
|
||||
${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
|
||||
${If} @CLIENT_COMPONENT_CONDITIONAL@
|
||||
CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\@INTERFACE_SHORTCUT_NAME@.lnk" \
|
||||
"$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"
|
||||
|
||||
|
@ -946,7 +1049,7 @@ Section "-Core installation"
|
|||
${EndIf}
|
||||
|
||||
; Conditional handling for server console shortcut
|
||||
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
|
||||
${If} @SERVER_COMPONENT_CONDITIONAL@
|
||||
CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" \
|
||||
"$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"
|
||||
${EndIf}
|
||||
|
@ -965,6 +1068,7 @@ Section "-Core installation"
|
|||
; Handle whichever post install options were set
|
||||
Call HandlePostInstallOptions
|
||||
|
||||
!insertmacro GoogleAnalytics "Installer" "Done" "" ""
|
||||
SectionEnd
|
||||
|
||||
!include nsProcess.nsh
|
||||
|
@ -979,7 +1083,7 @@ SectionEnd
|
|||
${If} $R0 == 0
|
||||
|
||||
; the process is running, ask the user to close it
|
||||
|
||||
|
||||
${If} "${displayName}" == "@CONSOLE_DISPLAY_NAME@"
|
||||
MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION \
|
||||
"${displayName} cannot be ${action} while ${displayName} is running.$\r$\nPlease close it in the system tray and click Retry to continue." \
|
||||
|
@ -992,6 +1096,8 @@ SectionEnd
|
|||
/SD IDCANCEL IDRETRY Prompt_${UniqueID} IDCANCEL 0
|
||||
${EndIf}
|
||||
|
||||
!insertmacro GoogleAnalytics "Installer" "Abort" "${displayName} Running" ""
|
||||
|
||||
; If the user decided to cancel, stop the current installer/uninstaller
|
||||
Abort
|
||||
|
||||
|
@ -1087,8 +1193,8 @@ Function .onSelChange
|
|||
!insertmacro SectionList MaybeSelectionChanged
|
||||
|
||||
; if neither component is selected, disable the install button
|
||||
${IfNot} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@}
|
||||
${AndIfNot} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
|
||||
${IfNot} @CLIENT_COMPONENT_CONDITIONAL@
|
||||
${AndIfNot} @SERVER_COMPONENT_CONDITIONAL@
|
||||
GetDlgItem $0 $HWNDPARENT 1
|
||||
EnableWindow $0 0
|
||||
${Else}
|
||||
|
@ -1219,6 +1325,11 @@ Function .onInit
|
|||
Quit
|
||||
!endif
|
||||
|
||||
!insertmacro InitGAClientID
|
||||
!insertmacro GetCampaignName $CampaignName
|
||||
|
||||
!insertmacro GoogleAnalytics "Installer" "Start" "$CampaignName" ""
|
||||
|
||||
; make sure none of the installed applications are still running
|
||||
!insertmacro CheckForRunningApplications "installed" "Installer"
|
||||
${nsProcess::Unload}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <QJsonArray>
|
||||
#include <QProcess>
|
||||
#include <QSharedMemory>
|
||||
#include <QRegularExpression>
|
||||
#include <QStandardPaths>
|
||||
#include <QTimer>
|
||||
#include <QUrlQuery>
|
||||
|
@ -727,7 +728,7 @@ void DomainServer::setupNodeListAndAssignments() {
|
|||
packetReceiver.registerListener(PacketType::OctreeDataPersist, this, "processOctreeDataPersistMessage");
|
||||
|
||||
packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacementRequest");
|
||||
packetReceiver.registerListener(PacketType::OctreeFileReplacementFromUrl, this, "handleOctreeFileReplacementFromURLRequest");
|
||||
packetReceiver.registerListener(PacketType::DomainContentReplacementFromUrl, this, "handleDomainContentReplacementFromURLRequest");
|
||||
|
||||
// set a custom packetVersionMatch as the verify packet operator for the udt::Socket
|
||||
nodeList->setPacketFilterOperator(&DomainServer::isPacketVerified);
|
||||
|
@ -736,7 +737,6 @@ void DomainServer::setupNodeListAndAssignments() {
|
|||
auto assetClient = DependencyManager::set<AssetClient>();
|
||||
assetClient->moveToThread(&_assetClientThread);
|
||||
_assetClientThread.start();
|
||||
|
||||
// add whatever static assignments that have been parsed to the queue
|
||||
addStaticAssignmentsToQueue();
|
||||
}
|
||||
|
@ -2136,7 +2136,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
} else if (url.path().startsWith(URI_API_BACKUPS_ID)) {
|
||||
auto id = url.path().mid(QString(URI_API_BACKUPS_ID).length());
|
||||
auto deferred = makePromise("consolidateBackup");
|
||||
deferred->then([connectionPtr, JSON_MIME_TYPE](QString error, QVariantMap result) {
|
||||
deferred->then([connectionPtr, JSON_MIME_TYPE, id](QString error, QVariantMap result) {
|
||||
if (!connectionPtr) {
|
||||
return;
|
||||
}
|
||||
|
@ -2147,7 +2147,14 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
auto path = result["backupFilePath"].toString();
|
||||
auto file { std::unique_ptr<QFile>(new QFile(path)) };
|
||||
if (file->open(QIODevice::ReadOnly)) {
|
||||
connectionPtr->respond(HTTPConnection::StatusCode200, std::move(file));
|
||||
constexpr const char* CONTENT_TYPE_ZIP = "application/zip";
|
||||
|
||||
auto downloadedFilename = id;
|
||||
downloadedFilename.replace(QRegularExpression(".zip$"), ".content.zip");
|
||||
auto contentDisposition = "attachment; filename=\"" + downloadedFilename + "\"";
|
||||
connectionPtr->respond(HTTPConnection::StatusCode200, std::move(file), CONTENT_TYPE_ZIP, {
|
||||
{ "Content-Disposition", contentDisposition.toUtf8() }
|
||||
});
|
||||
} else {
|
||||
qCritical(domain_server) << "Unable to load consolidated backup at:" << path << result;
|
||||
connectionPtr->respond(HTTPConnection::StatusCode500, "Error opening backup");
|
||||
|
@ -3429,13 +3436,10 @@ void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) {
|
|||
}
|
||||
}
|
||||
|
||||
void DomainServer::handleOctreeFileReplacementFromURLRequest(QSharedPointer<ReceivedMessage> message) {
|
||||
void DomainServer::handleDomainContentReplacementFromURLRequest(QSharedPointer<ReceivedMessage> message) {
|
||||
qInfo() << "Received request to replace content from a url";
|
||||
auto node = DependencyManager::get<LimitedNodeList>()->findNodeWithAddr(message->getSenderSockAddr());
|
||||
if (node) {
|
||||
qDebug() << "Found node: " << node->getCanReplaceContent();
|
||||
}
|
||||
if (node->getCanReplaceContent()) {
|
||||
if (node && node->getCanReplaceContent()) {
|
||||
// Convert message data into our URL
|
||||
QString url(message->getMessage());
|
||||
QUrl modelsURL = QUrl(url, QUrl::StrictMode);
|
||||
|
@ -3448,7 +3452,12 @@ void DomainServer::handleOctreeFileReplacementFromURLRequest(QSharedPointer<Rece
|
|||
connect(reply, &QNetworkReply::finished, [this, reply, modelsURL]() {
|
||||
QNetworkReply::NetworkError networkError = reply->error();
|
||||
if (networkError == QNetworkReply::NoError) {
|
||||
handleOctreeFileReplacement(reply->readAll());
|
||||
if (modelsURL.fileName().endsWith(".json.gz")) {
|
||||
handleOctreeFileReplacement(reply->readAll());
|
||||
} else if (modelsURL.fileName().endsWith(".zip")) {
|
||||
auto deferred = makePromise("recoverFromUploadedBackup");
|
||||
_contentManager->recoverFromUploadedBackup(deferred, reply->readAll());
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Error downloading JSON from specified file: " << modelsURL;
|
||||
}
|
||||
|
@ -3456,12 +3465,9 @@ void DomainServer::handleOctreeFileReplacementFromURLRequest(QSharedPointer<Rece
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void DomainServer::handleOctreeFileReplacementRequest(QSharedPointer<ReceivedMessage> message) {
|
||||
auto node = DependencyManager::get<NodeList>()->nodeWithUUID(message->getSourceID());
|
||||
if (node->getCanReplaceContent()) {
|
||||
handleOctreeFileReplacement(message->readAll());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -91,7 +91,7 @@ private slots:
|
|||
void processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processICEServerHeartbeatACK(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
void handleOctreeFileReplacementFromURLRequest(QSharedPointer<ReceivedMessage> message);
|
||||
void handleDomainContentReplacementFromURLRequest(QSharedPointer<ReceivedMessage> message);
|
||||
void handleOctreeFileReplacementRequest(QSharedPointer<ReceivedMessage> message);
|
||||
void handleOctreeFileReplacement(QByteArray octreeFile);
|
||||
|
||||
|
|
|
@ -26,17 +26,17 @@ generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources
|
|||
if (ANDROID)
|
||||
# on Android, don't compress the rcc binary
|
||||
add_custom_command(
|
||||
OUTPUT ${RESOURCES_RCC}
|
||||
DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS}
|
||||
COMMAND "${QT_DIR}/bin/rcc"
|
||||
ARGS ${RESOURCES_QRC} -no-compress -binary -o ${RESOURCES_RCC}
|
||||
OUTPUT ${RESOURCES_RCC}
|
||||
DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS}
|
||||
COMMAND "${QT_DIR}/bin/rcc"
|
||||
ARGS ${RESOURCES_QRC} -no-compress -binary -o ${RESOURCES_RCC}
|
||||
)
|
||||
else ()
|
||||
add_custom_command(
|
||||
OUTPUT ${RESOURCES_RCC}
|
||||
DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS}
|
||||
COMMAND "${QT_DIR}/bin/rcc"
|
||||
ARGS ${RESOURCES_QRC} -binary -o ${RESOURCES_RCC}
|
||||
OUTPUT ${RESOURCES_RCC}
|
||||
DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS}
|
||||
COMMAND "${QT_DIR}/bin/rcc"
|
||||
ARGS ${RESOURCES_QRC} -binary -o ${RESOURCES_RCC}
|
||||
)
|
||||
endif()
|
||||
|
||||
|
@ -191,7 +191,11 @@ add_dependencies(${TARGET_NAME} resources)
|
|||
if (WIN32)
|
||||
# These are external plugins, but we need to do the 'add dependency' here so that their
|
||||
# binary directories get added to the fixup path
|
||||
add_dependency_external_projects(sixense)
|
||||
|
||||
if (USE_SIXENSE)
|
||||
add_dependency_external_projects(sixense)
|
||||
endif ()
|
||||
|
||||
add_dependency_external_projects(sdl2)
|
||||
add_dependency_external_projects(OpenVR)
|
||||
add_dependency_external_projects(neuron)
|
||||
|
@ -199,12 +203,6 @@ if (WIN32)
|
|||
add_dependency_external_projects(steamworks)
|
||||
endif()
|
||||
|
||||
# include OPENSSL
|
||||
include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
|
||||
|
||||
# append OpenSSL to our list of libraries to link
|
||||
target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES})
|
||||
|
||||
# disable /OPT:REF and /OPT:ICF for the Debug builds
|
||||
# This will prevent the following linker warnings
|
||||
# LINK : warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:ICF' specification
|
||||
|
@ -228,6 +226,9 @@ link_hifi_libraries(
|
|||
# include the binary directory of render-utils for shader includes
|
||||
target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries/render-utils")
|
||||
|
||||
# include OpenSSL
|
||||
target_openssl()
|
||||
|
||||
target_bullet()
|
||||
target_opengl()
|
||||
add_crashpad()
|
||||
|
@ -328,13 +329,13 @@ if (APPLE)
|
|||
else()
|
||||
# copy the resources files beside the executable
|
||||
add_custom_command(TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
|
||||
"${RESOURCES_RCC}"
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
|
||||
"${RESOURCES_RCC}"
|
||||
"$<TARGET_FILE_DIR:interface>"
|
||||
# FIXME, the edit script code loads HTML from the scripts folder
|
||||
# which in turn relies on CSS that refers to the fonts. In theory
|
||||
# we should be able to modify the CSS to reference the QRC path to
|
||||
# the ttf files, but doing so generates a CORS policy violation,
|
||||
# we should be able to modify the CSS to reference the QRC path to
|
||||
# the ttf files, but doing so generates a CORS policy violation,
|
||||
# so we have to retain a copy of the fonts outside of the resources binary
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory
|
||||
"${PROJECT_SOURCE_DIR}/resources/fonts"
|
||||
|
@ -389,3 +390,6 @@ endif()
|
|||
add_dependency_external_projects(GifCreator)
|
||||
find_package(GifCreator REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${GIFCREATOR_INCLUDE_DIRS})
|
||||
|
||||
# tell CMake to exclude ui_console.h for policy CMP0071
|
||||
set_property(SOURCE ui_console.h PROPERTY SKIP_AUTOMOC ON)
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
{ "from": "TouchscreenVirtualPad.LX", "when": "!Application.CameraIndependent", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.TranslateX" },
|
||||
|
||||
{ "from": "TouchscreenVirtualPad.RX", "when": "!Application.CameraIndependent", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.Yaw" },
|
||||
{ "from": "TouchscreenVirtualPad.RX", "when": "!Application.CameraIndependent", "filters": [ {"type": "deadZone", "min": 0.05} , "invert" ], "to": "Actions.Yaw" },
|
||||
|
||||
{ "from": "TouchscreenVirtualPad.RY", "when": "!Application.CameraIndependent", "to": "Actions.Pitch" }
|
||||
{ "from": "TouchscreenVirtualPad.RY", "when": "!Application.CameraIndependent", "filters": [ {"type": "deadZone", "min": 0.05}, "invert" ], "to": "Actions.Pitch" }
|
||||
]
|
||||
}
|
||||
|
|
|
@ -38,7 +38,8 @@ ModalWindow {
|
|||
keyboardOverride: true // Disable ModalWindow's keyboard.
|
||||
|
||||
function tryDestroy() {
|
||||
root.destroy()
|
||||
Controller.setVPadHidden(false);
|
||||
root.destroy();
|
||||
}
|
||||
|
||||
LoginDialog {
|
||||
|
@ -52,8 +53,9 @@ ModalWindow {
|
|||
|
||||
Component.onCompleted: {
|
||||
this.anchors.centerIn = undefined;
|
||||
this.y=150;
|
||||
this.x=(parent.width - this.width)/2;
|
||||
this.y = 150;
|
||||
this.x = (parent.width - this.width) / 2;
|
||||
Controller.setVPadHidden(true);
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
|
|
|
@ -14,9 +14,9 @@ import Qt.labs.settings 1.0
|
|||
|
||||
import "./hifi/audio" as HifiAudio
|
||||
|
||||
Hifi.AvatarInputs {
|
||||
Item {
|
||||
id: root;
|
||||
objectName: "AvatarInputs"
|
||||
objectName: "AvatarInputsBar"
|
||||
property int modality: Qt.NonModal
|
||||
width: audio.width;
|
||||
height: audio.height;
|
||||
|
@ -26,7 +26,7 @@ Hifi.AvatarInputs {
|
|||
|
||||
HifiAudio.MicBar {
|
||||
id: audio;
|
||||
visible: root.showAudioTools;
|
||||
visible: AvatarInputs.showAudioTools;
|
||||
standalone: true;
|
||||
dragTarget: parent;
|
||||
}
|
|
@ -224,7 +224,7 @@ Item {
|
|||
|
||||
onClicked: {
|
||||
Qt.inputMethod.hide();
|
||||
root.destroy();
|
||||
root.tryDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ Overlay {
|
|||
font.family: "Helvetica"
|
||||
font.pixelSize: 18
|
||||
lineHeight: 18
|
||||
clip: true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"Entities": [
|
||||
{
|
||||
"type": "Box",
|
||||
"dimensions": {
|
||||
"x": 20,
|
||||
"y": 1,
|
||||
"z": 20
|
||||
},
|
||||
"position" : {
|
||||
"x": 0,
|
||||
"y": -12,
|
||||
"z": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"Version": 84
|
||||
}
|
|
@ -351,6 +351,7 @@ static const QString OBJ_EXTENSION = ".obj";
|
|||
static const QString AVA_JSON_EXTENSION = ".ava.json";
|
||||
static const QString WEB_VIEW_TAG = "noDownload=true";
|
||||
static const QString ZIP_EXTENSION = ".zip";
|
||||
static const QString CONTENT_ZIP_EXTENSION = ".content.zip";
|
||||
|
||||
static const float MIRROR_FULLSCREEN_DISTANCE = 0.389f;
|
||||
|
||||
|
@ -376,7 +377,7 @@ static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop";
|
|||
|
||||
static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
|
||||
|
||||
const QHash<QString, Application::AcceptURLMethod> Application::_acceptedExtensions {
|
||||
const std::vector<std::pair<QString, Application::AcceptURLMethod>> Application::_acceptedExtensions {
|
||||
{ SVO_EXTENSION, &Application::importSVOFromURL },
|
||||
{ SVO_JSON_EXTENSION, &Application::importSVOFromURL },
|
||||
{ AVA_JSON_EXTENSION, &Application::askToWearAvatarAttachmentUrl },
|
||||
|
@ -384,6 +385,7 @@ const QHash<QString, Application::AcceptURLMethod> Application::_acceptedExtensi
|
|||
{ JS_EXTENSION, &Application::askToLoadScript },
|
||||
{ FST_EXTENSION, &Application::askToSetAvatarUrl },
|
||||
{ JSON_GZ_EXTENSION, &Application::askToReplaceDomainContent },
|
||||
{ CONTENT_ZIP_EXTENSION, &Application::askToReplaceDomainContent },
|
||||
{ ZIP_EXTENSION, &Application::importFromZIP },
|
||||
{ JPG_EXTENSION, &Application::importImage },
|
||||
{ PNG_EXTENSION, &Application::importImage }
|
||||
|
@ -516,9 +518,11 @@ bool isDomainURL(QUrl url) {
|
|||
if (url.scheme() == URL_SCHEME_HIFI) {
|
||||
return true;
|
||||
}
|
||||
if (url.scheme() != URL_SCHEME_FILE &&
|
||||
url.scheme() != URL_SCHEME_HTTP &&
|
||||
url.scheme() != URL_SCHEME_HTTPS) {
|
||||
if (url.scheme() != URL_SCHEME_FILE) {
|
||||
// TODO -- once Octree::readFromURL no-longer takes over the main event-loop, serverless-domain urls can
|
||||
// be loaded over http(s)
|
||||
// && url.scheme() != URL_SCHEME_HTTP &&
|
||||
// url.scheme() != URL_SCHEME_HTTPS
|
||||
return false;
|
||||
}
|
||||
if (url.path().endsWith(".json", Qt::CaseInsensitive) ||
|
||||
|
@ -1178,8 +1182,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle);
|
||||
connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress);
|
||||
|
||||
connect(addressManager.data(), &AddressManager::urlHandled, this, &Application::setIsServerlessDomain);
|
||||
|
||||
connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateThreadPoolCount);
|
||||
connect(this, &Application::activeDisplayPluginChanged, this, [](){
|
||||
qApp->setProperty(hifi::properties::HMD, qApp->isHMDMode());
|
||||
|
@ -2729,6 +2731,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
|
|||
|
||||
surfaceContext->setContextProperty("ApplicationCompositor", &getApplicationCompositor());
|
||||
|
||||
surfaceContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
|
||||
surfaceContext->setContextProperty("Selection", DependencyManager::get<SelectionScriptingInterface>().data());
|
||||
surfaceContext->setContextProperty("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data());
|
||||
surfaceContext->setContextProperty("Wallet", DependencyManager::get<WalletScriptingInterface>().data());
|
||||
|
@ -2742,10 +2745,12 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
|
|||
|
||||
void Application::onDesktopRootItemCreated(QQuickItem* rootItem) {
|
||||
Stats::show();
|
||||
AvatarInputs::show();
|
||||
auto surfaceContext = DependencyManager::get<OffscreenUi>()->getSurfaceContext();
|
||||
surfaceContext->setContextProperty("Stats", Stats::getInstance());
|
||||
surfaceContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
|
||||
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto qml = PathUtils::qmlUrl("AvatarInputsBar.qml");
|
||||
offscreenUi->show(qml, "AvatarInputsBar");
|
||||
}
|
||||
|
||||
void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
|
||||
|
@ -3105,10 +3110,10 @@ bool Application::isServerlessMode() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
void Application::setIsServerlessDomain(bool isHifiScheme) {
|
||||
void Application::setIsServerlessMode(bool serverlessDomain) {
|
||||
auto tree = getEntities()->getTree();
|
||||
if (tree) {
|
||||
tree->setIsServerlessMode(!isHifiScheme);
|
||||
tree->setIsServerlessMode(serverlessDomain);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3118,22 +3123,17 @@ void Application::loadServerlessDomain(QUrl domainURL) {
|
|||
return;
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
clearDomainOctreeDetails();
|
||||
getMyAvatar()->setSessionUUID(QUuid()); // clear the sessionID
|
||||
NodePermissions permissions; // deny all permissions
|
||||
permissions.setAll(false);
|
||||
nodeList->setPermissions(permissions);
|
||||
|
||||
if (domainURL.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QUuid serverlessSessionID = QUuid::createUuid();
|
||||
getMyAvatar()->setSessionUUID(serverlessSessionID);
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->setSessionUUID(serverlessSessionID);
|
||||
|
||||
// there is no domain-server to tell us our permissions, so enable all
|
||||
NodePermissions permissions;
|
||||
permissions.setAll(true);
|
||||
nodeList->setPermissions(permissions);
|
||||
|
||||
|
@ -5875,17 +5875,21 @@ void Application::clearDomainAvatars() {
|
|||
}
|
||||
|
||||
void Application::domainURLChanged(QUrl domainURL) {
|
||||
updateWindowTitle();
|
||||
// disable physics until we have enough information about our new location to not cause craziness.
|
||||
resetPhysicsReadyInformation();
|
||||
if (domainURL.scheme() != URL_SCHEME_HIFI) {
|
||||
setIsServerlessMode(domainURL.scheme() != URL_SCHEME_HIFI);
|
||||
if (isServerlessMode()) {
|
||||
loadServerlessDomain(domainURL);
|
||||
}
|
||||
updateWindowTitle();
|
||||
}
|
||||
|
||||
|
||||
void Application::resettingDomain() {
|
||||
_notifiedPacketVersionMismatchThisDomain = false;
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
clearDomainOctreeDetails();
|
||||
}
|
||||
|
||||
void Application::nodeAdded(SharedNodePointer node) const {
|
||||
|
@ -6256,11 +6260,9 @@ bool Application::canAcceptURL(const QString& urlString) const {
|
|||
} else if (urlString.startsWith(URL_SCHEME_HIFI)) {
|
||||
return true;
|
||||
}
|
||||
QHashIterator<QString, AcceptURLMethod> i(_acceptedExtensions);
|
||||
QString lowerPath = url.path().toLower();
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
if (lowerPath.endsWith(i.key(), Qt::CaseInsensitive)) {
|
||||
for (auto& pair : _acceptedExtensions) {
|
||||
if (lowerPath.endsWith(pair.first, Qt::CaseInsensitive)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -6276,12 +6278,10 @@ bool Application::acceptURL(const QString& urlString, bool defaultUpload) {
|
|||
return true;
|
||||
}
|
||||
|
||||
QHashIterator<QString, AcceptURLMethod> i(_acceptedExtensions);
|
||||
QString lowerPath = url.path().toLower();
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
if (lowerPath.endsWith(i.key(), Qt::CaseInsensitive)) {
|
||||
AcceptURLMethod method = i.value();
|
||||
for (auto& pair : _acceptedExtensions) {
|
||||
if (lowerPath.endsWith(pair.first, Qt::CaseInsensitive)) {
|
||||
AcceptURLMethod method = pair.second;
|
||||
return (this->*method)(urlString);
|
||||
}
|
||||
}
|
||||
|
@ -6468,13 +6468,11 @@ void Application::replaceDomainContent(const QString& url) {
|
|||
QByteArray urlData(url.toUtf8());
|
||||
auto limitedNodeList = DependencyManager::get<NodeList>();
|
||||
const auto& domainHandler = limitedNodeList->getDomainHandler();
|
||||
limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) {
|
||||
return node->getType() == NodeType::EntityServer && node->getActiveSocket();
|
||||
}, [&urlData, limitedNodeList, &domainHandler](const SharedNodePointer& octreeNode) {
|
||||
auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true);
|
||||
octreeFilePacket->write(urlData);
|
||||
limitedNodeList->sendPacket(std::move(octreeFilePacket), domainHandler.getSockAddr());
|
||||
});
|
||||
|
||||
auto octreeFilePacket = NLPacket::create(PacketType::DomainContentReplacementFromUrl, urlData.size(), true);
|
||||
octreeFilePacket->write(urlData);
|
||||
limitedNodeList->sendPacket(std::move(octreeFilePacket), domainHandler.getSockAddr());
|
||||
|
||||
auto addressManager = DependencyManager::get<AddressManager>();
|
||||
addressManager->handleLookupString(DOMAIN_SPAWNING_POINT);
|
||||
QString newHomeAddress = addressManager->getHost() + DOMAIN_SPAWNING_POINT;
|
||||
|
@ -7432,10 +7430,35 @@ bool Application::isThrottleRendering() const {
|
|||
}
|
||||
|
||||
bool Application::hasFocus() const {
|
||||
if (_displayPlugin) {
|
||||
return getActiveDisplayPlugin()->hasFocus();
|
||||
bool result = (QApplication::activeWindow() != nullptr);
|
||||
#if defined(Q_OS_WIN)
|
||||
// On Windows, QWidget::activateWindow() - as called in setFocus() - makes the application's taskbar icon flash but doesn't
|
||||
// take user focus away from their current window. So also check whether the application is the user's current foreground
|
||||
// window.
|
||||
result = result && (HWND)QApplication::activeWindow()->winId() == GetForegroundWindow();
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
void Application::setFocus() {
|
||||
// Note: Windows doesn't allow a user focus to be taken away from another application. Instead, it changes the color of and
|
||||
// flashes the taskbar icon.
|
||||
auto window = qApp->getWindow();
|
||||
window->activateWindow();
|
||||
}
|
||||
|
||||
void Application::raise() {
|
||||
auto windowState = qApp->getWindow()->windowState();
|
||||
if (windowState & Qt::WindowMinimized) {
|
||||
if (windowState & Qt::WindowMaximized) {
|
||||
qApp->getWindow()->showMaximized();
|
||||
} else if (windowState & Qt::WindowFullScreen) {
|
||||
qApp->getWindow()->showFullScreen();
|
||||
} else {
|
||||
qApp->getWindow()->showNormal();
|
||||
}
|
||||
}
|
||||
return (QApplication::activeWindow() != nullptr);
|
||||
qApp->getWindow()->raise();
|
||||
}
|
||||
|
||||
void Application::setMaxOctreePacketsPerSecond(int maxOctreePPS) {
|
||||
|
|
|
@ -161,6 +161,8 @@ public:
|
|||
QRect getRecommendedHUDRect() const;
|
||||
glm::vec2 getDeviceSize() const;
|
||||
bool hasFocus() const;
|
||||
void setFocus();
|
||||
void raise();
|
||||
|
||||
void showCursor(const Cursor::Icon& cursor);
|
||||
|
||||
|
@ -390,7 +392,7 @@ public slots:
|
|||
const QString getPreferredCursor() const { return _preferredCursor.get(); }
|
||||
void setPreferredCursor(const QString& cursor);
|
||||
|
||||
void setIsServerlessDomain(bool serverlessDomain);
|
||||
void setIsServerlessMode(bool serverlessDomain);
|
||||
void loadServerlessDomain(QUrl domainURL);
|
||||
|
||||
Q_INVOKABLE bool askBeforeSetAvatarUrl(const QString& avatarUrl) { return askToSetAvatarUrl(avatarUrl); }
|
||||
|
@ -607,7 +609,7 @@ private:
|
|||
GLCanvas* _glWidget{ nullptr };
|
||||
|
||||
typedef bool (Application::* AcceptURLMethod)(const QString &);
|
||||
static const QHash<QString, AcceptURLMethod> _acceptedExtensions;
|
||||
static const std::vector<std::pair<QString, Application::AcceptURLMethod>> _acceptedExtensions;
|
||||
|
||||
glm::uvec2 _renderResolution;
|
||||
|
||||
|
|
|
@ -74,16 +74,14 @@ QScriptValue WindowScriptingInterface::hasFocus() {
|
|||
void WindowScriptingInterface::setFocus() {
|
||||
// It's forbidden to call focus() from another thread.
|
||||
qApp->postLambdaEvent([] {
|
||||
auto window = qApp->getWindow();
|
||||
window->activateWindow();
|
||||
window->setFocus();
|
||||
qApp->setFocus();
|
||||
});
|
||||
}
|
||||
|
||||
void WindowScriptingInterface::raiseMainWindow() {
|
||||
// It's forbidden to call raise() from another thread.
|
||||
qApp->postLambdaEvent([] {
|
||||
qApp->getWindow()->raise();
|
||||
qApp->raise();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -62,13 +62,14 @@ public slots:
|
|||
QScriptValue hasFocus();
|
||||
|
||||
/**jsdoc
|
||||
* Make the Interface window have focus.
|
||||
* Make the Interface window have focus. On Windows, if Interface doesn't already have focus, the task bar icon flashes to
|
||||
* indicate that Interface wants attention but focus isn't taken away from the application that the user is using.
|
||||
* @function Window.setFocus
|
||||
*/
|
||||
void setFocus();
|
||||
|
||||
/**jsdoc
|
||||
* Raise the Interface window if it is minimized, and give it focus.
|
||||
* Raise the Interface window if it is minimized. If raised, the window gains focus.
|
||||
* @function Window.raiseMainWindow
|
||||
*/
|
||||
void raiseMainWindow();
|
||||
|
|
|
@ -58,7 +58,7 @@ void AddressBarDialog::loadHome() {
|
|||
qDebug() << "Called LoadHome";
|
||||
auto locationBookmarks = DependencyManager::get<LocationBookmarks>();
|
||||
QString homeLocation = locationBookmarks->addressForBookmark(LocationBookmarks::HOME_BOOKMARK);
|
||||
const QString DEFAULT_HOME_LOCATION = "file:///~/serverless/start.json";
|
||||
const QString DEFAULT_HOME_LOCATION = "localhost";
|
||||
if (homeLocation == "") {
|
||||
homeLocation = DEFAULT_HOME_LOCATION;
|
||||
}
|
||||
|
|
|
@ -16,19 +16,19 @@
|
|||
#include "Application.h"
|
||||
#include "Menu.h"
|
||||
|
||||
HIFI_QML_DEF(AvatarInputs)
|
||||
|
||||
static AvatarInputs* INSTANCE{ nullptr };
|
||||
|
||||
Setting::Handle<bool> showAudioToolsSetting { QStringList { "AvatarInputs", "showAudioTools" }, false };
|
||||
|
||||
AvatarInputs* AvatarInputs::getInstance() {
|
||||
Q_ASSERT(INSTANCE);
|
||||
if (!INSTANCE) {
|
||||
INSTANCE = new AvatarInputs();
|
||||
Q_ASSERT(INSTANCE);
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
AvatarInputs::AvatarInputs(QQuickItem* parent) : QQuickItem(parent) {
|
||||
INSTANCE = this;
|
||||
AvatarInputs::AvatarInputs(QObject* parent) : QObject(parent) {
|
||||
_showAudioTools = showAudioToolsSetting.get();
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ public: \
|
|||
private: \
|
||||
type _##name{ initialValue };
|
||||
|
||||
class AvatarInputs : public QQuickItem {
|
||||
class AvatarInputs : public QObject {
|
||||
Q_OBJECT
|
||||
HIFI_QML_DECL
|
||||
|
||||
|
@ -32,7 +32,7 @@ class AvatarInputs : public QQuickItem {
|
|||
public:
|
||||
static AvatarInputs* getInstance();
|
||||
Q_INVOKABLE float loudnessToAudioLevel(float loudness);
|
||||
AvatarInputs(QQuickItem* parent = nullptr);
|
||||
AvatarInputs(QObject* parent = nullptr);
|
||||
void update();
|
||||
bool showAudioTools() const { return _showAudioTools; }
|
||||
|
||||
|
|
|
@ -37,7 +37,8 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) :
|
|||
_ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection),
|
||||
_drawInFront(base3DOverlay->_drawInFront),
|
||||
_drawHUDLayer(base3DOverlay->_drawHUDLayer),
|
||||
_isGrabbable(base3DOverlay->_isGrabbable)
|
||||
_isGrabbable(base3DOverlay->_isGrabbable),
|
||||
_isVisibleInSecondaryCamera(base3DOverlay->_isVisibleInSecondaryCamera)
|
||||
{
|
||||
setTransform(base3DOverlay->getTransform());
|
||||
}
|
||||
|
@ -142,6 +143,13 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
|||
setIsGrabbable(isGrabbable.toBool());
|
||||
}
|
||||
|
||||
auto isVisibleInSecondaryCamera = properties["isVisibleInSecondaryCamera"];
|
||||
if (isVisibleInSecondaryCamera.isValid()) {
|
||||
bool value = isVisibleInSecondaryCamera.toBool();
|
||||
setIsVisibleInSecondaryCamera(value);
|
||||
needRenderItemUpdate = true;
|
||||
}
|
||||
|
||||
if (properties["position"].isValid()) {
|
||||
setLocalPosition(vec3FromVariant(properties["position"]));
|
||||
needRenderItemUpdate = true;
|
||||
|
@ -221,6 +229,8 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
|||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
|
||||
* @property {boolean} isVisibleInSecondaryCamera=false - If <code>true</code>, the overlay is rendered in secondary
|
||||
* camera views.
|
||||
* @property {Uuid} parentID=null - The avatar, entity, or overlay that the overlay is parented to.
|
||||
* @property {number} parentJointIndex=65535 - Integer value specifying the skeleton joint that the overlay is attached to if
|
||||
* <code>parentID</code> is an avatar skeleton. A value of <code>65535</code> means "no joint".
|
||||
|
@ -259,6 +269,9 @@ QVariant Base3DOverlay::getProperty(const QString& property) {
|
|||
if (property == "grabbable") {
|
||||
return _isGrabbable;
|
||||
}
|
||||
if (property == "isVisibleInSecondaryCamera") {
|
||||
return _isVisibleInSecondaryCamera;
|
||||
}
|
||||
if (property == "parentID") {
|
||||
return getParentID();
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ public:
|
|||
bool getDrawInFront() const { return _drawInFront; }
|
||||
bool getDrawHUDLayer() const { return _drawHUDLayer; }
|
||||
bool getIsGrabbable() const { return _isGrabbable; }
|
||||
virtual bool getIsVisibleInSecondaryCamera() const override { return _isVisibleInSecondaryCamera; }
|
||||
|
||||
void setIsSolid(bool isSolid) { _isSolid = isSolid; }
|
||||
void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; }
|
||||
|
@ -55,6 +56,7 @@ public:
|
|||
virtual void setDrawInFront(bool value) { _drawInFront = value; }
|
||||
virtual void setDrawHUDLayer(bool value) { _drawHUDLayer = value; }
|
||||
void setIsGrabbable(bool value) { _isGrabbable = value; }
|
||||
virtual void setIsVisibleInSecondaryCamera(bool value) { _isVisibleInSecondaryCamera = value; }
|
||||
|
||||
virtual AABox getBounds() const override = 0;
|
||||
|
||||
|
@ -92,6 +94,7 @@ protected:
|
|||
bool _drawInFront;
|
||||
bool _drawHUDLayer;
|
||||
bool _isGrabbable { false };
|
||||
bool _isVisibleInSecondaryCamera { false };
|
||||
mutable bool _renderVariableDirty { true };
|
||||
|
||||
QString _name;
|
||||
|
|
|
@ -89,8 +89,11 @@ void ModelOverlay::update(float deltatime) {
|
|||
}
|
||||
if (_visibleDirty) {
|
||||
_visibleDirty = false;
|
||||
// don't show overlays in mirrors
|
||||
_model->setVisibleInScene(getVisible(), scene, render::ItemKey::TAG_BITS_0, false);
|
||||
// don't show overlays in mirrors or spectator-cam unless _isVisibleInSecondaryCamera is true
|
||||
_model->setVisibleInScene(getVisible(), scene,
|
||||
render::ItemKey::TAG_BITS_0 |
|
||||
(_isVisibleInSecondaryCamera ? render::ItemKey::TAG_BITS_1 : render::ItemKey::TAG_BITS_NONE),
|
||||
false);
|
||||
}
|
||||
if (_drawInFrontDirty) {
|
||||
_drawInFrontDirty = false;
|
||||
|
|
|
@ -36,6 +36,11 @@ public:
|
|||
void clearSubRenderItemIDs();
|
||||
void setSubRenderItemIDs(const render::ItemIDs& ids);
|
||||
|
||||
virtual void setIsVisibleInSecondaryCamera(bool value) override {
|
||||
Base3DOverlay::setIsVisibleInSecondaryCamera(value);
|
||||
_visibleDirty = true;
|
||||
}
|
||||
|
||||
void setProperties(const QVariantMap& properties) override;
|
||||
QVariant getProperty(const QString& property) override;
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
|
|
|
@ -56,6 +56,8 @@ public:
|
|||
bool isLoaded() { return _isLoaded; }
|
||||
bool getVisible() const { return _visible; }
|
||||
virtual bool isTransparent() { return getAlphaPulse() != 0.0f || getAlpha() != 1.0f; };
|
||||
virtual bool getIsVisibleInSecondaryCamera() const { return false; }
|
||||
|
||||
xColor getColor();
|
||||
float getAlpha();
|
||||
|
||||
|
|
|
@ -49,7 +49,11 @@ namespace render {
|
|||
builder.withInvisible();
|
||||
}
|
||||
|
||||
builder.withTagBits(render::ItemKey::TAG_BITS_0); // Only draw overlays in main view
|
||||
// always visible in primary view. if isVisibleInSecondaryCamera, also draw in secondary view
|
||||
uint32_t viewTaskBits = render::ItemKey::TAG_BITS_0 |
|
||||
(overlay->getIsVisibleInSecondaryCamera() ? render::ItemKey::TAG_BITS_1 : render::ItemKey::TAG_BITS_NONE);
|
||||
|
||||
builder.withTagBits(viewTaskBits);
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
|
|
@ -114,7 +114,7 @@ void Text3DOverlay::render(RenderArgs* args) {
|
|||
|
||||
glm::vec3 topLeft(-halfDimensions.x, -halfDimensions.y, SLIGHTLY_BEHIND);
|
||||
glm::vec3 bottomRight(halfDimensions.x, halfDimensions.y, SLIGHTLY_BEHIND);
|
||||
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, false, quadColor.a < 1.0f, false, false, false, false);
|
||||
DependencyManager::get<GeometryCache>()->bindSimpleProgram(batch, false, quadColor.a < 1.0f, false, false, false);
|
||||
DependencyManager::get<GeometryCache>()->renderQuad(batch, topLeft, bottomRight, quadColor, _geometryId);
|
||||
|
||||
// Same font properties as textSize()
|
||||
|
|
|
@ -40,8 +40,10 @@ QUrl const TextOverlay::URL(QString("hifi/overlays/TextOverlay.qml"));
|
|||
*
|
||||
* @property {number} margin=0 - Sets the <code>leftMargin</code> and <code>topMargin</code> values, in pixels.
|
||||
* <em>Write-only.</em>
|
||||
* @property {number} leftMargin=0 - The left margin's size, in pixels. <em>Write-only.</em>
|
||||
* @property {number} topMargin=0 - The top margin's size, in pixels. <em>Write-only.</em>
|
||||
* @property {number} leftMargin=0 - The left margin's size, in pixels. This value is also used for the right margin.
|
||||
* <em>Write-only.</em>
|
||||
* @property {number} topMargin=0 - The top margin's size, in pixels. This value is also used for the bottom margin.
|
||||
* <em>Write-only.</em>
|
||||
* @property {string} text="" - The text to display. Text does not automatically wrap; use <code>\n</code> for a line break. Text
|
||||
* is clipped to the <code>bounds</code>. <em>Write-only.</em>
|
||||
* @property {number} font.size=18 - The size of the text, in pixels. <em>Write-only.</em>
|
||||
|
|
|
@ -63,6 +63,19 @@ static const float OPAQUE_ALPHA_THRESHOLD = 0.99f;
|
|||
|
||||
const QString Web3DOverlay::TYPE = "web3d";
|
||||
const QString Web3DOverlay::QML = "Web3DOverlay.qml";
|
||||
|
||||
static auto qmlSurfaceDeleter = [](OffscreenQmlSurface* surface) {
|
||||
AbstractViewStateInterface::instance()->postLambdaEvent([surface] {
|
||||
if (AbstractViewStateInterface::instance()->isAboutToQuit()) {
|
||||
// WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown
|
||||
// if the application has already stopped its event loop, delete must be explicit
|
||||
delete surface;
|
||||
} else {
|
||||
surface->deleteLater();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Web3DOverlay::Web3DOverlay() {
|
||||
_touchDevice.setCapabilities(QTouchDevice::Position);
|
||||
_touchDevice.setType(QTouchDevice::TouchScreen);
|
||||
|
@ -75,7 +88,8 @@ Web3DOverlay::Web3DOverlay() {
|
|||
connect(this, &Web3DOverlay::resizeWebSurface, this, &Web3DOverlay::onResizeWebSurface);
|
||||
|
||||
//need to be intialized before Tablet 1st open
|
||||
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(_url);
|
||||
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(QML);
|
||||
_cachedWebSurface = true;
|
||||
_webSurface->getSurfaceContext()->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
|
||||
_webSurface->getSurfaceContext()->setContextProperty("Account", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
|
||||
_webSurface->getSurfaceContext()->setContextProperty("GlobalServices", AccountServicesScriptingInterface::getInstance()); // DEPRECATED - TO BE REMOVED
|
||||
|
@ -114,6 +128,7 @@ void Web3DOverlay::destroyWebSurface() {
|
|||
if (!_webSurface) {
|
||||
return;
|
||||
}
|
||||
|
||||
QQuickItem* rootItem = _webSurface->getRootItem();
|
||||
|
||||
if (rootItem && rootItem->objectName() == "tabletRoot") {
|
||||
|
@ -135,10 +150,15 @@ void Web3DOverlay::destroyWebSurface() {
|
|||
|
||||
QObject::disconnect(this, &Web3DOverlay::scriptEventReceived, _webSurface.data(), &OffscreenQmlSurface::emitScriptEvent);
|
||||
QObject::disconnect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived);
|
||||
auto offscreenCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
|
||||
// FIXME prevents crash on shutdown, but we shoudln't have to do this check
|
||||
if (offscreenCache) {
|
||||
offscreenCache->release(QML, _webSurface);
|
||||
|
||||
// If the web surface was fetched out of the cache, release it back into the cache
|
||||
if (_cachedWebSurface) {
|
||||
auto offscreenCache = DependencyManager::get<OffscreenQmlSurfaceCache>();
|
||||
// FIXME prevents crash on shutdown, but we shoudln't have to do this check
|
||||
if (offscreenCache) {
|
||||
offscreenCache->release(QML, _webSurface);
|
||||
}
|
||||
_cachedWebSurface = false;
|
||||
}
|
||||
_webSurface.reset();
|
||||
}
|
||||
|
@ -147,6 +167,8 @@ void Web3DOverlay::buildWebSurface() {
|
|||
if (_webSurface) {
|
||||
return;
|
||||
}
|
||||
// FIXME the context save here is most likely unecessary since the QML surfaces now render
|
||||
// off the main thread, and all GL context work is done off the main thread (I *think*)
|
||||
gl::withSavedContext([&] {
|
||||
// FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces
|
||||
// and the current rendering load)
|
||||
|
@ -156,10 +178,13 @@ void Web3DOverlay::buildWebSurface() {
|
|||
|
||||
if (isWebContent()) {
|
||||
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(QML);
|
||||
_cachedWebSurface = true;
|
||||
_webSurface->getRootItem()->setProperty("url", _url);
|
||||
_webSurface->getRootItem()->setProperty("scriptURL", _scriptURL);
|
||||
} else {
|
||||
_webSurface = DependencyManager::get<OffscreenQmlSurfaceCache>()->acquire(_url);
|
||||
_webSurface = QSharedPointer<OffscreenQmlSurface>(new OffscreenQmlSurface(), qmlSurfaceDeleter);
|
||||
_webSurface->load(_url);
|
||||
_cachedWebSurface = false;
|
||||
setupQmlSurface();
|
||||
}
|
||||
_webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getWorldPosition()));
|
||||
|
|
|
@ -88,6 +88,7 @@ private:
|
|||
|
||||
InputMode _inputMode { Touch };
|
||||
QSharedPointer<OffscreenQmlSurface> _webSurface;
|
||||
bool _cachedWebSurface{ false };
|
||||
gpu::TexturePointer _texture;
|
||||
QString _url;
|
||||
QString _scriptURL;
|
||||
|
|
|
@ -66,16 +66,12 @@ bool ElbowConstraint::apply(glm::quat& rotation) const {
|
|||
bool twistWasClamped = (twistAngle != clampedTwistAngle);
|
||||
|
||||
// update rotation
|
||||
const float MIN_SWING_REAL_PART = 0.99999f;
|
||||
if (twistWasClamped || fabsf(swingRotation.w) < MIN_SWING_REAL_PART) {
|
||||
if (twistWasClamped) {
|
||||
twistRotation = glm::angleAxis(clampedTwistAngle, _axis);
|
||||
}
|
||||
// we discard all swing and only keep twist
|
||||
rotation = twistRotation * _referenceRotation;
|
||||
return true;
|
||||
if (twistWasClamped) {
|
||||
twistRotation = glm::angleAxis(clampedTwistAngle, _axis);
|
||||
}
|
||||
return false;
|
||||
// we discard all swing and only keep twist
|
||||
rotation = twistRotation * _referenceRotation;
|
||||
return true;
|
||||
}
|
||||
|
||||
glm::quat ElbowConstraint::computeCenterRotation() const {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -33,65 +33,8 @@
|
|||
|
||||
#include "FBXBaker.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning( push )
|
||||
#pragma warning( disable : 4267 )
|
||||
#endif
|
||||
|
||||
#include <draco/mesh/triangle_soup_mesh_builder.h>
|
||||
#include <draco/compression/encode.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning( pop )
|
||||
#endif
|
||||
|
||||
|
||||
FBXBaker::FBXBaker(const QUrl& fbxURL, TextureBakerThreadGetter textureThreadGetter,
|
||||
const QString& bakedOutputDir, const QString& originalOutputDir) :
|
||||
_fbxURL(fbxURL),
|
||||
_bakedOutputDir(bakedOutputDir),
|
||||
_originalOutputDir(originalOutputDir),
|
||||
_textureThreadGetter(textureThreadGetter)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
FBXBaker::~FBXBaker() {
|
||||
if (_tempDir.exists()) {
|
||||
if (!_tempDir.remove(_originalFBXFilePath)) {
|
||||
qCWarning(model_baking) << "Failed to remove temporary copy of fbx file:" << _originalFBXFilePath;
|
||||
}
|
||||
if (!_tempDir.rmdir(".")) {
|
||||
qCWarning(model_baking) << "Failed to remove temporary directory:" << _tempDir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FBXBaker::abort() {
|
||||
Baker::abort();
|
||||
|
||||
// tell our underlying TextureBaker instances to abort
|
||||
// the FBXBaker will wait until all are aborted before emitting its own abort signal
|
||||
for (auto& textureBaker : _bakingTextures) {
|
||||
textureBaker->abort();
|
||||
}
|
||||
}
|
||||
|
||||
void FBXBaker::bake() {
|
||||
qDebug() << "FBXBaker" << _fbxURL << "bake starting";
|
||||
|
||||
auto tempDir = PathUtils::generateTemporaryDir();
|
||||
|
||||
if (tempDir.isEmpty()) {
|
||||
handleError("Failed to create a temporary directory.");
|
||||
return;
|
||||
}
|
||||
|
||||
_tempDir = tempDir;
|
||||
|
||||
_originalFBXFilePath = _tempDir.filePath(_fbxURL.fileName());
|
||||
qDebug() << "Made temporary dir " << _tempDir;
|
||||
qDebug() << "Origin file path: " << _originalFBXFilePath;
|
||||
void FBXBaker::bake() {
|
||||
qDebug() << "FBXBaker" << _modelURL << "bake starting";
|
||||
|
||||
// setup the output folder for the results of this bake
|
||||
setupOutputFolder();
|
||||
|
@ -152,7 +95,7 @@ void FBXBaker::setupOutputFolder() {
|
|||
}
|
||||
// attempt to make the output folder
|
||||
if (!QDir().mkpath(_originalOutputDir)) {
|
||||
handleError("Failed to create FBX output folder " + _bakedOutputDir);
|
||||
handleError("Failed to create FBX output folder " + _originalOutputDir);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -160,25 +103,25 @@ void FBXBaker::setupOutputFolder() {
|
|||
|
||||
void FBXBaker::loadSourceFBX() {
|
||||
// check if the FBX is local or first needs to be downloaded
|
||||
if (_fbxURL.isLocalFile()) {
|
||||
if (_modelURL.isLocalFile()) {
|
||||
// load up the local file
|
||||
QFile localFBX { _fbxURL.toLocalFile() };
|
||||
QFile localFBX { _modelURL.toLocalFile() };
|
||||
|
||||
qDebug() << "Local file url: " << _fbxURL << _fbxURL.toString() << _fbxURL.toLocalFile() << ", copying to: " << _originalFBXFilePath;
|
||||
qDebug() << "Local file url: " << _modelURL << _modelURL.toString() << _modelURL.toLocalFile() << ", copying to: " << _originalModelFilePath;
|
||||
|
||||
if (!localFBX.exists()) {
|
||||
//QMessageBox::warning(this, "Could not find " + _fbxURL.toString(), "");
|
||||
handleError("Could not find " + _fbxURL.toString());
|
||||
handleError("Could not find " + _modelURL.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
// make a copy in the output folder
|
||||
if (!_originalOutputDir.isEmpty()) {
|
||||
qDebug() << "Copying to: " << _originalOutputDir << "/" << _fbxURL.fileName();
|
||||
localFBX.copy(_originalOutputDir + "/" + _fbxURL.fileName());
|
||||
qDebug() << "Copying to: " << _originalOutputDir << "/" << _modelURL.fileName();
|
||||
localFBX.copy(_originalOutputDir + "/" + _modelURL.fileName());
|
||||
}
|
||||
|
||||
localFBX.copy(_originalFBXFilePath);
|
||||
localFBX.copy(_originalModelFilePath);
|
||||
|
||||
// emit our signal to start the import of the FBX source copy
|
||||
emit sourceCopyReadyToLoad();
|
||||
|
@ -193,9 +136,9 @@ void FBXBaker::loadSourceFBX() {
|
|||
networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
|
||||
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
|
||||
networkRequest.setUrl(_fbxURL);
|
||||
networkRequest.setUrl(_modelURL);
|
||||
|
||||
qCDebug(model_baking) << "Downloading" << _fbxURL;
|
||||
qCDebug(model_baking) << "Downloading" << _modelURL;
|
||||
auto networkReply = networkAccessManager.get(networkRequest);
|
||||
|
||||
connect(networkReply, &QNetworkReply::finished, this, &FBXBaker::handleFBXNetworkReply);
|
||||
|
@ -206,20 +149,20 @@ void FBXBaker::handleFBXNetworkReply() {
|
|||
auto requestReply = qobject_cast<QNetworkReply*>(sender());
|
||||
|
||||
if (requestReply->error() == QNetworkReply::NoError) {
|
||||
qCDebug(model_baking) << "Downloaded" << _fbxURL;
|
||||
qCDebug(model_baking) << "Downloaded" << _modelURL;
|
||||
|
||||
// grab the contents of the reply and make a copy in the output folder
|
||||
QFile copyOfOriginal(_originalFBXFilePath);
|
||||
QFile copyOfOriginal(_originalModelFilePath);
|
||||
|
||||
qDebug(model_baking) << "Writing copy of original FBX to" << _originalFBXFilePath << copyOfOriginal.fileName();
|
||||
qDebug(model_baking) << "Writing copy of original FBX to" << _originalModelFilePath << copyOfOriginal.fileName();
|
||||
|
||||
if (!copyOfOriginal.open(QIODevice::WriteOnly)) {
|
||||
// add an error to the error list for this FBX stating that a duplicate of the original FBX could not be made
|
||||
handleError("Could not create copy of " + _fbxURL.toString() + " (Failed to open " + _originalFBXFilePath + ")");
|
||||
handleError("Could not create copy of " + _modelURL.toString() + " (Failed to open " + _originalModelFilePath + ")");
|
||||
return;
|
||||
}
|
||||
if (copyOfOriginal.write(requestReply->readAll()) == -1) {
|
||||
handleError("Could not create copy of " + _fbxURL.toString() + " (Failed to write)");
|
||||
handleError("Could not create copy of " + _modelURL.toString() + " (Failed to write)");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -227,98 +170,32 @@ void FBXBaker::handleFBXNetworkReply() {
|
|||
copyOfOriginal.close();
|
||||
|
||||
if (!_originalOutputDir.isEmpty()) {
|
||||
copyOfOriginal.copy(_originalOutputDir + "/" + _fbxURL.fileName());
|
||||
copyOfOriginal.copy(_originalOutputDir + "/" + _modelURL.fileName());
|
||||
}
|
||||
|
||||
// emit our signal to start the import of the FBX source copy
|
||||
emit sourceCopyReadyToLoad();
|
||||
} else {
|
||||
// add an error to our list stating that the FBX could not be downloaded
|
||||
handleError("Failed to download " + _fbxURL.toString());
|
||||
handleError("Failed to download " + _modelURL.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void FBXBaker::importScene() {
|
||||
qDebug() << "file path: " << _originalFBXFilePath.toLocal8Bit().data() << QDir(_originalFBXFilePath).exists();
|
||||
qDebug() << "file path: " << _originalModelFilePath.toLocal8Bit().data() << QDir(_originalModelFilePath).exists();
|
||||
|
||||
QFile fbxFile(_originalFBXFilePath);
|
||||
QFile fbxFile(_originalModelFilePath);
|
||||
if (!fbxFile.open(QIODevice::ReadOnly)) {
|
||||
handleError("Error opening " + _originalFBXFilePath + " for reading");
|
||||
handleError("Error opening " + _originalModelFilePath + " for reading");
|
||||
return;
|
||||
}
|
||||
|
||||
FBXReader reader;
|
||||
|
||||
qCDebug(model_baking) << "Parsing" << _fbxURL;
|
||||
qCDebug(model_baking) << "Parsing" << _modelURL;
|
||||
_rootNode = reader._rootNode = reader.parseFBX(&fbxFile);
|
||||
_geometry = reader.extractFBXGeometry({}, _fbxURL.toString());
|
||||
_textureContent = reader._textureContent;
|
||||
}
|
||||
|
||||
QString texturePathRelativeToFBX(QUrl fbxURL, QUrl textureURL) {
|
||||
auto fbxPath = fbxURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
auto texturePath = textureURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
|
||||
if (texturePath.startsWith(fbxPath)) {
|
||||
// texture path is a child of the FBX path, return the texture path without the fbx path
|
||||
return texturePath.mid(fbxPath.length());
|
||||
} else {
|
||||
// the texture path was not a child of the FBX path, return the empty string
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
QString FBXBaker::createBakedTextureFileName(const QFileInfo& textureFileInfo) {
|
||||
// first make sure we have a unique base name for this texture
|
||||
// in case another texture referenced by this model has the same base name
|
||||
auto& nameMatches = _textureNameMatchCount[textureFileInfo.baseName()];
|
||||
|
||||
QString bakedTextureFileName { textureFileInfo.completeBaseName() };
|
||||
|
||||
if (nameMatches > 0) {
|
||||
// there are already nameMatches texture with this name
|
||||
// append - and that number to our baked texture file name so that it is unique
|
||||
bakedTextureFileName += "-" + QString::number(nameMatches);
|
||||
}
|
||||
|
||||
bakedTextureFileName += BAKED_TEXTURE_EXT;
|
||||
|
||||
// increment the number of name matches
|
||||
++nameMatches;
|
||||
|
||||
return bakedTextureFileName;
|
||||
}
|
||||
|
||||
QUrl FBXBaker::getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded) {
|
||||
|
||||
QUrl urlToTexture;
|
||||
|
||||
auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/"));
|
||||
|
||||
if (isEmbedded) {
|
||||
urlToTexture = _fbxURL.toString() + "/" + apparentRelativePath.filePath();
|
||||
} else {
|
||||
if (textureFileInfo.exists() && textureFileInfo.isFile()) {
|
||||
// set the texture URL to the local texture that we have confirmed exists
|
||||
urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath());
|
||||
} else {
|
||||
// external texture that we'll need to download or find
|
||||
|
||||
// this is a relative file path which will require different handling
|
||||
// depending on the location of the original FBX
|
||||
if (_fbxURL.isLocalFile() && apparentRelativePath.exists() && apparentRelativePath.isFile()) {
|
||||
// the absolute path we ran into for the texture in the FBX exists on this machine
|
||||
// so use that file
|
||||
urlToTexture = QUrl::fromLocalFile(apparentRelativePath.absoluteFilePath());
|
||||
} else {
|
||||
// we didn't find the texture on this machine at the absolute path
|
||||
// so assume that it is right beside the FBX to match the behaviour of interface
|
||||
urlToTexture = _fbxURL.resolved(apparentRelativePath.fileName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return urlToTexture;
|
||||
_geometry = reader.extractFBXGeometry({}, _modelURL.toString());
|
||||
_textureContentMap = reader._textureContent;
|
||||
}
|
||||
|
||||
void FBXBaker::rewriteAndBakeSceneModels() {
|
||||
|
@ -344,176 +221,25 @@ void FBXBaker::rewriteAndBakeSceneModels() {
|
|||
|
||||
// TODO Pull this out of _geometry instead so we don't have to reprocess it
|
||||
auto extractedMesh = FBXReader::extractMesh(objectChild, meshIndex, false);
|
||||
auto& mesh = extractedMesh.mesh;
|
||||
|
||||
if (mesh.wasCompressed) {
|
||||
handleError("Cannot re-bake a file that contains compressed mesh");
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(mesh.normals.size() == 0 || mesh.normals.size() == mesh.vertices.size());
|
||||
Q_ASSERT(mesh.colors.size() == 0 || mesh.colors.size() == mesh.vertices.size());
|
||||
Q_ASSERT(mesh.texCoords.size() == 0 || mesh.texCoords.size() == mesh.vertices.size());
|
||||
|
||||
int64_t numTriangles { 0 };
|
||||
for (auto& part : mesh.parts) {
|
||||
if ((part.quadTrianglesIndices.size() % 3) != 0 || (part.triangleIndices.size() % 3) != 0) {
|
||||
handleWarning("Found a mesh part with invalid index data, skipping");
|
||||
|
||||
// Callback to get MaterialID
|
||||
GetMaterialIDCallback materialIDcallback = [&extractedMesh](int partIndex) {
|
||||
return extractedMesh.partMaterialTextures[partIndex].first;
|
||||
};
|
||||
|
||||
// Compress mesh information and store in dracoMeshNode
|
||||
FBXNode dracoMeshNode;
|
||||
bool success = compressMesh(extractedMesh.mesh, hasDeformers, dracoMeshNode, materialIDcallback);
|
||||
|
||||
// if bake fails - return, if there were errors and continue, if there were warnings.
|
||||
if (!success) {
|
||||
if (hasErrors()) {
|
||||
return;
|
||||
} else if (hasWarnings()) {
|
||||
continue;
|
||||
}
|
||||
numTriangles += part.quadTrianglesIndices.size() / 3;
|
||||
numTriangles += part.triangleIndices.size() / 3;
|
||||
}
|
||||
|
||||
if (numTriangles == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
draco::TriangleSoupMeshBuilder meshBuilder;
|
||||
|
||||
meshBuilder.Start(numTriangles);
|
||||
|
||||
bool hasNormals { mesh.normals.size() > 0 };
|
||||
bool hasColors { mesh.colors.size() > 0 };
|
||||
bool hasTexCoords { mesh.texCoords.size() > 0 };
|
||||
bool hasTexCoords1 { mesh.texCoords1.size() > 0 };
|
||||
bool hasPerFaceMaterials { mesh.parts.size() > 1
|
||||
|| extractedMesh.partMaterialTextures[0].first != 0 };
|
||||
bool needsOriginalIndices { hasDeformers };
|
||||
|
||||
int normalsAttributeID { -1 };
|
||||
int colorsAttributeID { -1 };
|
||||
int texCoordsAttributeID { -1 };
|
||||
int texCoords1AttributeID { -1 };
|
||||
int faceMaterialAttributeID { -1 };
|
||||
int originalIndexAttributeID { -1 };
|
||||
|
||||
const int positionAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::POSITION,
|
||||
3, draco::DT_FLOAT32);
|
||||
if (needsOriginalIndices) {
|
||||
originalIndexAttributeID = meshBuilder.AddAttribute(
|
||||
(draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_ORIGINAL_INDEX,
|
||||
1, draco::DT_INT32);
|
||||
}
|
||||
|
||||
if (hasNormals) {
|
||||
normalsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::NORMAL,
|
||||
3, draco::DT_FLOAT32);
|
||||
}
|
||||
if (hasColors) {
|
||||
colorsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::COLOR,
|
||||
3, draco::DT_FLOAT32);
|
||||
}
|
||||
if (hasTexCoords) {
|
||||
texCoordsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::TEX_COORD,
|
||||
2, draco::DT_FLOAT32);
|
||||
}
|
||||
if (hasTexCoords1) {
|
||||
texCoords1AttributeID = meshBuilder.AddAttribute(
|
||||
(draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_TEX_COORD_1,
|
||||
2, draco::DT_FLOAT32);
|
||||
}
|
||||
if (hasPerFaceMaterials) {
|
||||
faceMaterialAttributeID = meshBuilder.AddAttribute(
|
||||
(draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_MATERIAL_ID,
|
||||
1, draco::DT_UINT16);
|
||||
}
|
||||
|
||||
|
||||
auto partIndex = 0;
|
||||
draco::FaceIndex face;
|
||||
for (auto& part : mesh.parts) {
|
||||
const auto& matTex = extractedMesh.partMaterialTextures[partIndex];
|
||||
uint16_t materialID = matTex.first;
|
||||
|
||||
auto addFace = [&](QVector<int>& indices, int index, draco::FaceIndex face) {
|
||||
int32_t idx0 = indices[index];
|
||||
int32_t idx1 = indices[index + 1];
|
||||
int32_t idx2 = indices[index + 2];
|
||||
|
||||
if (hasPerFaceMaterials) {
|
||||
meshBuilder.SetPerFaceAttributeValueForFace(faceMaterialAttributeID, face, &materialID);
|
||||
}
|
||||
|
||||
meshBuilder.SetAttributeValuesForFace(positionAttributeID, face,
|
||||
&mesh.vertices[idx0], &mesh.vertices[idx1],
|
||||
&mesh.vertices[idx2]);
|
||||
|
||||
if (needsOriginalIndices) {
|
||||
meshBuilder.SetAttributeValuesForFace(originalIndexAttributeID, face,
|
||||
&mesh.originalIndices[idx0],
|
||||
&mesh.originalIndices[idx1],
|
||||
&mesh.originalIndices[idx2]);
|
||||
}
|
||||
if (hasNormals) {
|
||||
meshBuilder.SetAttributeValuesForFace(normalsAttributeID, face,
|
||||
&mesh.normals[idx0], &mesh.normals[idx1],
|
||||
&mesh.normals[idx2]);
|
||||
}
|
||||
if (hasColors) {
|
||||
meshBuilder.SetAttributeValuesForFace(colorsAttributeID, face,
|
||||
&mesh.colors[idx0], &mesh.colors[idx1],
|
||||
&mesh.colors[idx2]);
|
||||
}
|
||||
if (hasTexCoords) {
|
||||
meshBuilder.SetAttributeValuesForFace(texCoordsAttributeID, face,
|
||||
&mesh.texCoords[idx0], &mesh.texCoords[idx1],
|
||||
&mesh.texCoords[idx2]);
|
||||
}
|
||||
if (hasTexCoords1) {
|
||||
meshBuilder.SetAttributeValuesForFace(texCoords1AttributeID, face,
|
||||
&mesh.texCoords1[idx0], &mesh.texCoords1[idx1],
|
||||
&mesh.texCoords1[idx2]);
|
||||
}
|
||||
};
|
||||
|
||||
for (int i = 0; (i + 2) < part.quadTrianglesIndices.size(); i += 3) {
|
||||
addFace(part.quadTrianglesIndices, i, face++);
|
||||
}
|
||||
|
||||
for (int i = 0; (i + 2) < part.triangleIndices.size(); i += 3) {
|
||||
addFace(part.triangleIndices, i, face++);
|
||||
}
|
||||
|
||||
partIndex++;
|
||||
}
|
||||
|
||||
auto dracoMesh = meshBuilder.Finalize();
|
||||
|
||||
if (!dracoMesh) {
|
||||
handleWarning("Failed to finalize the baking of a draco Geometry node");
|
||||
continue;
|
||||
}
|
||||
|
||||
// we need to modify unique attribute IDs for custom attributes
|
||||
// so the attributes are easily retrievable on the other side
|
||||
if (hasPerFaceMaterials) {
|
||||
dracoMesh->attribute(faceMaterialAttributeID)->set_unique_id(DRACO_ATTRIBUTE_MATERIAL_ID);
|
||||
}
|
||||
|
||||
if (hasTexCoords1) {
|
||||
dracoMesh->attribute(texCoords1AttributeID)->set_unique_id(DRACO_ATTRIBUTE_TEX_COORD_1);
|
||||
}
|
||||
|
||||
if (needsOriginalIndices) {
|
||||
dracoMesh->attribute(originalIndexAttributeID)->set_unique_id(DRACO_ATTRIBUTE_ORIGINAL_INDEX);
|
||||
}
|
||||
|
||||
draco::Encoder encoder;
|
||||
|
||||
encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 14);
|
||||
encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, 12);
|
||||
encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 10);
|
||||
encoder.SetSpeedOptions(0, 5);
|
||||
|
||||
draco::EncoderBuffer buffer;
|
||||
encoder.EncodeMeshToBuffer(*dracoMesh, &buffer);
|
||||
|
||||
FBXNode dracoMeshNode;
|
||||
dracoMeshNode.name = "DracoMesh";
|
||||
auto value = QVariant::fromValue(QByteArray(buffer.data(), (int) buffer.size()));
|
||||
dracoMeshNode.properties.append(value);
|
||||
|
||||
|
||||
objectChild.children.push_back(dracoMeshNode);
|
||||
|
||||
static const std::vector<QString> nodeNamesToDelete {
|
||||
|
@ -590,69 +316,25 @@ void FBXBaker::rewriteAndBakeSceneTextures() {
|
|||
for (FBXNode& textureChild : object->children) {
|
||||
|
||||
if (textureChild.name == "RelativeFilename") {
|
||||
QString fbxTextureFileName { textureChild.properties.at(0).toString() };
|
||||
|
||||
// grab the ID for this texture so we can figure out the
|
||||
// texture type from the loaded materials
|
||||
auto textureID { object->properties[0].toString() };
|
||||
auto textureType = textureTypes[textureID];
|
||||
|
||||
// use QFileInfo to easily split up the existing texture filename into its components
|
||||
QString fbxTextureFileName { textureChild.properties.at(0).toByteArray() };
|
||||
QFileInfo textureFileInfo { fbxTextureFileName.replace("\\", "/") };
|
||||
// Compress the texture information and return the new filename to be added into the FBX scene
|
||||
auto bakedTextureFile = compressTexture(fbxTextureFileName, textureType);
|
||||
|
||||
if (hasErrors()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (textureFileInfo.suffix() == BAKED_TEXTURE_EXT.mid(1)) {
|
||||
// re-baking an FBX that already references baked textures is a fail
|
||||
// so we add an error and return from here
|
||||
handleError("Cannot re-bake a file that references compressed textures");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!image::getSupportedFormats().contains(textureFileInfo.suffix())) {
|
||||
// this is a texture format we don't bake, skip it
|
||||
handleWarning(fbxTextureFileName + " is not a bakeable texture format");
|
||||
continue;
|
||||
}
|
||||
|
||||
// make sure this texture points to something and isn't one we've already re-mapped
|
||||
if (!textureFileInfo.filePath().isEmpty()) {
|
||||
// check if this was an embedded texture we have already have in-memory content for
|
||||
auto textureContent = _textureContent.value(fbxTextureFileName.toLocal8Bit());
|
||||
|
||||
// figure out the URL to this texture, embedded or external
|
||||
auto urlToTexture = getTextureURL(textureFileInfo, fbxTextureFileName,
|
||||
!textureContent.isNull());
|
||||
|
||||
QString bakedTextureFileName;
|
||||
if (_remappedTexturePaths.contains(urlToTexture)) {
|
||||
bakedTextureFileName = _remappedTexturePaths[urlToTexture];
|
||||
} else {
|
||||
// construct the new baked texture file name and file path
|
||||
// ensuring that the baked texture will have a unique name
|
||||
// even if there was another texture with the same name at a different path
|
||||
bakedTextureFileName = createBakedTextureFileName(textureFileInfo);
|
||||
_remappedTexturePaths[urlToTexture] = bakedTextureFileName;
|
||||
}
|
||||
|
||||
qCDebug(model_baking).noquote() << "Re-mapping" << fbxTextureFileName
|
||||
<< "to" << bakedTextureFileName;
|
||||
|
||||
QString bakedTextureFilePath {
|
||||
_bakedOutputDir + "/" + bakedTextureFileName
|
||||
};
|
||||
|
||||
// write the new filename into the FBX scene
|
||||
textureChild.properties[0] = bakedTextureFileName.toLocal8Bit();
|
||||
|
||||
if (!_bakingTextures.contains(urlToTexture)) {
|
||||
_outputFiles.push_back(bakedTextureFilePath);
|
||||
|
||||
// grab the ID for this texture so we can figure out the
|
||||
// texture type from the loaded materials
|
||||
QString textureID { object->properties[0].toByteArray() };
|
||||
auto textureType = textureTypes[textureID];
|
||||
|
||||
// bake this texture asynchronously
|
||||
bakeTexture(urlToTexture, textureType, _bakedOutputDir, bakedTextureFileName, textureContent);
|
||||
// If no errors or warnings have occurred during texture compression add the filename to the FBX scene
|
||||
if (!bakedTextureFile.isNull()) {
|
||||
textureChild.properties[0] = bakedTextureFile;
|
||||
} else {
|
||||
// if bake fails - return, if there were errors and continue, if there were warnings.
|
||||
if (hasErrors()) {
|
||||
return;
|
||||
} else if (hasWarnings()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -671,172 +353,26 @@ void FBXBaker::rewriteAndBakeSceneTextures() {
|
|||
}
|
||||
}
|
||||
|
||||
void FBXBaker::bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType,
|
||||
const QDir& outputDir, const QString& bakedFilename, const QByteArray& textureContent) {
|
||||
// start a bake for this texture and add it to our list to keep track of
|
||||
QSharedPointer<TextureBaker> bakingTexture {
|
||||
new TextureBaker(textureURL, textureType, outputDir, bakedFilename, textureContent),
|
||||
&TextureBaker::deleteLater
|
||||
};
|
||||
|
||||
// make sure we hear when the baking texture is done or aborted
|
||||
connect(bakingTexture.data(), &Baker::finished, this, &FBXBaker::handleBakedTexture);
|
||||
connect(bakingTexture.data(), &TextureBaker::aborted, this, &FBXBaker::handleAbortedTexture);
|
||||
|
||||
// keep a shared pointer to the baking texture
|
||||
_bakingTextures.insert(textureURL, bakingTexture);
|
||||
|
||||
// start baking the texture on one of our available worker threads
|
||||
bakingTexture->moveToThread(_textureThreadGetter());
|
||||
QMetaObject::invokeMethod(bakingTexture.data(), "bake");
|
||||
}
|
||||
|
||||
void FBXBaker::handleBakedTexture() {
|
||||
TextureBaker* bakedTexture = qobject_cast<TextureBaker*>(sender());
|
||||
|
||||
// make sure we haven't already run into errors, and that this is a valid texture
|
||||
if (bakedTexture) {
|
||||
if (!shouldStop()) {
|
||||
if (!bakedTexture->hasErrors()) {
|
||||
if (!_originalOutputDir.isEmpty()) {
|
||||
// we've been asked to make copies of the originals, so we need to make copies of this if it is a linked texture
|
||||
|
||||
// use the path to the texture being baked to determine if this was an embedded or a linked texture
|
||||
|
||||
// it is embeddded if the texure being baked was inside a folder with the name of the FBX
|
||||
// since that is the fake URL we provide when baking external textures
|
||||
|
||||
if (!_fbxURL.isParentOf(bakedTexture->getTextureURL())) {
|
||||
// for linked textures we want to save a copy of original texture beside the original FBX
|
||||
|
||||
qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL();
|
||||
|
||||
// check if we have a relative path to use for the texture
|
||||
auto relativeTexturePath = texturePathRelativeToFBX(_fbxURL, bakedTexture->getTextureURL());
|
||||
|
||||
QFile originalTextureFile {
|
||||
_originalOutputDir + "/" + relativeTexturePath + bakedTexture->getTextureURL().fileName()
|
||||
};
|
||||
|
||||
if (relativeTexturePath.length() > 0) {
|
||||
// make the folders needed by the relative path
|
||||
}
|
||||
|
||||
if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) {
|
||||
qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName()
|
||||
<< "for" << _fbxURL;
|
||||
} else {
|
||||
handleError("Could not save original external texture " + originalTextureFile.fileName()
|
||||
+ " for " + _fbxURL.toString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// now that this texture has been baked and handled, we can remove that TextureBaker from our hash
|
||||
_bakingTextures.remove(bakedTexture->getTextureURL());
|
||||
|
||||
checkIfTexturesFinished();
|
||||
} else {
|
||||
// there was an error baking this texture - add it to our list of errors
|
||||
_errorList.append(bakedTexture->getErrors());
|
||||
|
||||
// we don't emit finished yet so that the other textures can finish baking first
|
||||
_pendingErrorEmission = true;
|
||||
|
||||
// now that this texture has been baked, even though it failed, we can remove that TextureBaker from our list
|
||||
_bakingTextures.remove(bakedTexture->getTextureURL());
|
||||
|
||||
// abort any other ongoing texture bakes since we know we'll end up failing
|
||||
for (auto& bakingTexture : _bakingTextures) {
|
||||
bakingTexture->abort();
|
||||
}
|
||||
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
} else {
|
||||
// we have errors to attend to, so we don't do extra processing for this texture
|
||||
// but we do need to remove that TextureBaker from our list
|
||||
// and then check if we're done with all textures
|
||||
_bakingTextures.remove(bakedTexture->getTextureURL());
|
||||
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FBXBaker::handleAbortedTexture() {
|
||||
// grab the texture bake that was aborted and remove it from our hash since we don't need to track it anymore
|
||||
TextureBaker* bakedTexture = qobject_cast<TextureBaker*>(sender());
|
||||
|
||||
if (bakedTexture) {
|
||||
_bakingTextures.remove(bakedTexture->getTextureURL());
|
||||
}
|
||||
|
||||
// since a texture we were baking aborted, our status is also aborted
|
||||
_shouldAbort.store(true);
|
||||
|
||||
// abort any other ongoing texture bakes since we know we'll end up failing
|
||||
for (auto& bakingTexture : _bakingTextures) {
|
||||
bakingTexture->abort();
|
||||
}
|
||||
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
|
||||
void FBXBaker::exportScene() {
|
||||
// save the relative path to this FBX inside our passed output folder
|
||||
auto fileName = _fbxURL.fileName();
|
||||
auto fileName = _modelURL.fileName();
|
||||
auto baseName = fileName.left(fileName.lastIndexOf('.'));
|
||||
auto bakedFilename = baseName + BAKED_FBX_EXTENSION;
|
||||
|
||||
_bakedFBXFilePath = _bakedOutputDir + "/" + bakedFilename;
|
||||
_bakedModelFilePath = _bakedOutputDir + "/" + bakedFilename;
|
||||
|
||||
auto fbxData = FBXWriter::encodeFBX(_rootNode);
|
||||
|
||||
QFile bakedFile(_bakedFBXFilePath);
|
||||
QFile bakedFile(_bakedModelFilePath);
|
||||
|
||||
if (!bakedFile.open(QIODevice::WriteOnly)) {
|
||||
handleError("Error opening " + _bakedFBXFilePath + " for writing");
|
||||
handleError("Error opening " + _bakedModelFilePath + " for writing");
|
||||
return;
|
||||
}
|
||||
|
||||
bakedFile.write(fbxData);
|
||||
|
||||
_outputFiles.push_back(_bakedFBXFilePath);
|
||||
_outputFiles.push_back(_bakedModelFilePath);
|
||||
|
||||
qCDebug(model_baking) << "Exported" << _fbxURL << "with re-written paths to" << _bakedFBXFilePath;
|
||||
}
|
||||
|
||||
void FBXBaker::checkIfTexturesFinished() {
|
||||
// check if we're done everything we need to do for this FBX
|
||||
// and emit our finished signal if we're done
|
||||
|
||||
if (_bakingTextures.isEmpty()) {
|
||||
if (shouldStop()) {
|
||||
// if we're checking for completion but we have errors
|
||||
// that means one or more of our texture baking operations failed
|
||||
|
||||
if (_pendingErrorEmission) {
|
||||
setIsFinished(true);
|
||||
}
|
||||
|
||||
return;
|
||||
} else {
|
||||
qCDebug(model_baking) << "Finished baking, emitting finsihed" << _fbxURL;
|
||||
|
||||
setIsFinished(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FBXBaker::setWasAborted(bool wasAborted) {
|
||||
if (wasAborted != _wasAborted.load()) {
|
||||
Baker::setWasAborted(wasAborted);
|
||||
|
||||
if (wasAborted) {
|
||||
qCDebug(model_baking) << "Aborted baking" << _fbxURL;
|
||||
}
|
||||
}
|
||||
qCDebug(model_baking) << "Exported" << _modelURL << "with re-written paths to" << _bakedModelFilePath;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
#include "Baker.h"
|
||||
#include "TextureBaker.h"
|
||||
|
||||
#include "ModelBaker.h"
|
||||
#include "ModelBakingLoggingCategory.h"
|
||||
|
||||
#include <gpu/Texture.h>
|
||||
|
@ -30,21 +30,13 @@ static const QString BAKED_FBX_EXTENSION = ".baked.fbx";
|
|||
|
||||
using TextureBakerThreadGetter = std::function<QThread*()>;
|
||||
|
||||
class FBXBaker : public Baker {
|
||||
class FBXBaker : public ModelBaker {
|
||||
Q_OBJECT
|
||||
public:
|
||||
FBXBaker(const QUrl& fbxURL, TextureBakerThreadGetter textureThreadGetter,
|
||||
const QString& bakedOutputDir, const QString& originalOutputDir = "");
|
||||
~FBXBaker() override;
|
||||
|
||||
QUrl getFBXUrl() const { return _fbxURL; }
|
||||
QString getBakedFBXFilePath() const { return _bakedFBXFilePath; }
|
||||
|
||||
virtual void setWasAborted(bool wasAborted) override;
|
||||
using ModelBaker::ModelBaker;
|
||||
|
||||
public slots:
|
||||
virtual void bake() override;
|
||||
virtual void abort() override;
|
||||
|
||||
signals:
|
||||
void sourceCopyReadyToLoad();
|
||||
|
@ -52,8 +44,6 @@ signals:
|
|||
private slots:
|
||||
void bakeSourceCopy();
|
||||
void handleFBXNetworkReply();
|
||||
void handleBakedTexture();
|
||||
void handleAbortedTexture();
|
||||
|
||||
private:
|
||||
void setupOutputFolder();
|
||||
|
@ -64,38 +54,12 @@ private:
|
|||
void rewriteAndBakeSceneModels();
|
||||
void rewriteAndBakeSceneTextures();
|
||||
void exportScene();
|
||||
void removeEmbeddedMediaFolder();
|
||||
|
||||
void checkIfTexturesFinished();
|
||||
|
||||
QString createBakedTextureFileName(const QFileInfo& textureFileInfo);
|
||||
QUrl getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded = false);
|
||||
|
||||
void bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType, const QDir& outputDir,
|
||||
const QString& bakedFilename, const QByteArray& textureContent = QByteArray());
|
||||
|
||||
QUrl _fbxURL;
|
||||
|
||||
FBXNode _rootNode;
|
||||
FBXGeometry* _geometry;
|
||||
QHash<QByteArray, QByteArray> _textureContent;
|
||||
|
||||
QString _bakedFBXFilePath;
|
||||
|
||||
QString _bakedOutputDir;
|
||||
|
||||
// If set, the original FBX and textures will also be copied here
|
||||
QString _originalOutputDir;
|
||||
|
||||
QDir _tempDir;
|
||||
QString _originalFBXFilePath;
|
||||
|
||||
QMultiHash<QUrl, QSharedPointer<TextureBaker>> _bakingTextures;
|
||||
QHash<QString, int> _textureNameMatchCount;
|
||||
QHash<QUrl, QString> _remappedTexturePaths;
|
||||
|
||||
TextureBakerThreadGetter _textureThreadGetter;
|
||||
|
||||
bool _pendingErrorEmission { false };
|
||||
};
|
||||
|
||||
|
|
521
libraries/baking/src/ModelBaker.cpp
Normal file
521
libraries/baking/src/ModelBaker.cpp
Normal file
|
@ -0,0 +1,521 @@
|
|||
//
|
||||
// ModelBaker.cpp
|
||||
// libraries/baking/src
|
||||
//
|
||||
// Created by Utkarsh Gautam on 9/29/17.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "ModelBaker.h"
|
||||
|
||||
#include <PathUtils.h>
|
||||
|
||||
#include <FBXReader.h>
|
||||
#include <FBXWriter.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning( push )
|
||||
#pragma warning( disable : 4267 )
|
||||
#endif
|
||||
|
||||
#include <draco/mesh/triangle_soup_mesh_builder.h>
|
||||
#include <draco/compression/encode.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning( pop )
|
||||
#endif
|
||||
|
||||
ModelBaker::ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory) :
|
||||
_modelURL(inputModelURL),
|
||||
_bakedOutputDir(bakedOutputDirectory),
|
||||
_originalOutputDir(originalOutputDirectory),
|
||||
_textureThreadGetter(inputTextureThreadGetter)
|
||||
{
|
||||
auto tempDir = PathUtils::generateTemporaryDir();
|
||||
|
||||
if (tempDir.isEmpty()) {
|
||||
handleError("Failed to create a temporary directory.");
|
||||
return;
|
||||
}
|
||||
|
||||
_modelTempDir = tempDir;
|
||||
|
||||
_originalModelFilePath = _modelTempDir.filePath(_modelURL.fileName());
|
||||
qDebug() << "Made temporary dir " << _modelTempDir;
|
||||
qDebug() << "Origin file path: " << _originalModelFilePath;
|
||||
|
||||
}
|
||||
|
||||
ModelBaker::~ModelBaker() {
|
||||
if (_modelTempDir.exists()) {
|
||||
if (!_modelTempDir.remove(_originalModelFilePath)) {
|
||||
qCWarning(model_baking) << "Failed to remove temporary copy of fbx file:" << _originalModelFilePath;
|
||||
}
|
||||
if (!_modelTempDir.rmdir(".")) {
|
||||
qCWarning(model_baking) << "Failed to remove temporary directory:" << _modelTempDir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::abort() {
|
||||
Baker::abort();
|
||||
|
||||
// tell our underlying TextureBaker instances to abort
|
||||
// the ModelBaker will wait until all are aborted before emitting its own abort signal
|
||||
for (auto& textureBaker : _bakingTextures) {
|
||||
textureBaker->abort();
|
||||
}
|
||||
}
|
||||
|
||||
bool ModelBaker::compressMesh(FBXMesh& mesh, bool hasDeformers, FBXNode& dracoMeshNode, GetMaterialIDCallback materialIDCallback) {
|
||||
if (mesh.wasCompressed) {
|
||||
handleError("Cannot re-bake a file that contains compressed mesh");
|
||||
return false;
|
||||
}
|
||||
|
||||
Q_ASSERT(mesh.normals.size() == 0 || mesh.normals.size() == mesh.vertices.size());
|
||||
Q_ASSERT(mesh.colors.size() == 0 || mesh.colors.size() == mesh.vertices.size());
|
||||
Q_ASSERT(mesh.texCoords.size() == 0 || mesh.texCoords.size() == mesh.vertices.size());
|
||||
|
||||
int64_t numTriangles{ 0 };
|
||||
for (auto& part : mesh.parts) {
|
||||
if ((part.quadTrianglesIndices.size() % 3) != 0 || (part.triangleIndices.size() % 3) != 0) {
|
||||
handleWarning("Found a mesh part with invalid index data, skipping");
|
||||
continue;
|
||||
}
|
||||
numTriangles += part.quadTrianglesIndices.size() / 3;
|
||||
numTriangles += part.triangleIndices.size() / 3;
|
||||
}
|
||||
|
||||
if (numTriangles == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
draco::TriangleSoupMeshBuilder meshBuilder;
|
||||
|
||||
meshBuilder.Start(numTriangles);
|
||||
|
||||
bool hasNormals{ mesh.normals.size() > 0 };
|
||||
bool hasColors{ mesh.colors.size() > 0 };
|
||||
bool hasTexCoords{ mesh.texCoords.size() > 0 };
|
||||
bool hasTexCoords1{ mesh.texCoords1.size() > 0 };
|
||||
bool hasPerFaceMaterials = (materialIDCallback) ? (mesh.parts.size() > 1 || materialIDCallback(0) != 0 ) : true;
|
||||
bool needsOriginalIndices{ hasDeformers };
|
||||
|
||||
int normalsAttributeID { -1 };
|
||||
int colorsAttributeID { -1 };
|
||||
int texCoordsAttributeID { -1 };
|
||||
int texCoords1AttributeID { -1 };
|
||||
int faceMaterialAttributeID { -1 };
|
||||
int originalIndexAttributeID { -1 };
|
||||
|
||||
const int positionAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::POSITION,
|
||||
3, draco::DT_FLOAT32);
|
||||
if (needsOriginalIndices) {
|
||||
originalIndexAttributeID = meshBuilder.AddAttribute(
|
||||
(draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_ORIGINAL_INDEX,
|
||||
1, draco::DT_INT32);
|
||||
}
|
||||
|
||||
if (hasNormals) {
|
||||
normalsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::NORMAL,
|
||||
3, draco::DT_FLOAT32);
|
||||
}
|
||||
if (hasColors) {
|
||||
colorsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::COLOR,
|
||||
3, draco::DT_FLOAT32);
|
||||
}
|
||||
if (hasTexCoords) {
|
||||
texCoordsAttributeID = meshBuilder.AddAttribute(draco::GeometryAttribute::TEX_COORD,
|
||||
2, draco::DT_FLOAT32);
|
||||
}
|
||||
if (hasTexCoords1) {
|
||||
texCoords1AttributeID = meshBuilder.AddAttribute(
|
||||
(draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_TEX_COORD_1,
|
||||
2, draco::DT_FLOAT32);
|
||||
}
|
||||
if (hasPerFaceMaterials) {
|
||||
faceMaterialAttributeID = meshBuilder.AddAttribute(
|
||||
(draco::GeometryAttribute::Type)DRACO_ATTRIBUTE_MATERIAL_ID,
|
||||
1, draco::DT_UINT16);
|
||||
}
|
||||
|
||||
auto partIndex = 0;
|
||||
draco::FaceIndex face;
|
||||
uint16_t materialID;
|
||||
|
||||
for (auto& part : mesh.parts) {
|
||||
materialID = (materialIDCallback) ? materialIDCallback(partIndex) : partIndex;
|
||||
|
||||
auto addFace = [&](QVector<int>& indices, int index, draco::FaceIndex face) {
|
||||
int32_t idx0 = indices[index];
|
||||
int32_t idx1 = indices[index + 1];
|
||||
int32_t idx2 = indices[index + 2];
|
||||
|
||||
if (hasPerFaceMaterials) {
|
||||
meshBuilder.SetPerFaceAttributeValueForFace(faceMaterialAttributeID, face, &materialID);
|
||||
}
|
||||
|
||||
meshBuilder.SetAttributeValuesForFace(positionAttributeID, face,
|
||||
&mesh.vertices[idx0], &mesh.vertices[idx1],
|
||||
&mesh.vertices[idx2]);
|
||||
|
||||
if (needsOriginalIndices) {
|
||||
meshBuilder.SetAttributeValuesForFace(originalIndexAttributeID, face,
|
||||
&mesh.originalIndices[idx0],
|
||||
&mesh.originalIndices[idx1],
|
||||
&mesh.originalIndices[idx2]);
|
||||
}
|
||||
if (hasNormals) {
|
||||
meshBuilder.SetAttributeValuesForFace(normalsAttributeID, face,
|
||||
&mesh.normals[idx0], &mesh.normals[idx1],
|
||||
&mesh.normals[idx2]);
|
||||
}
|
||||
if (hasColors) {
|
||||
meshBuilder.SetAttributeValuesForFace(colorsAttributeID, face,
|
||||
&mesh.colors[idx0], &mesh.colors[idx1],
|
||||
&mesh.colors[idx2]);
|
||||
}
|
||||
if (hasTexCoords) {
|
||||
meshBuilder.SetAttributeValuesForFace(texCoordsAttributeID, face,
|
||||
&mesh.texCoords[idx0], &mesh.texCoords[idx1],
|
||||
&mesh.texCoords[idx2]);
|
||||
}
|
||||
if (hasTexCoords1) {
|
||||
meshBuilder.SetAttributeValuesForFace(texCoords1AttributeID, face,
|
||||
&mesh.texCoords1[idx0], &mesh.texCoords1[idx1],
|
||||
&mesh.texCoords1[idx2]);
|
||||
}
|
||||
};
|
||||
|
||||
for (int i = 0; (i + 2) < part.quadTrianglesIndices.size(); i += 3) {
|
||||
addFace(part.quadTrianglesIndices, i, face++);
|
||||
}
|
||||
|
||||
for (int i = 0; (i + 2) < part.triangleIndices.size(); i += 3) {
|
||||
addFace(part.triangleIndices, i, face++);
|
||||
}
|
||||
|
||||
partIndex++;
|
||||
}
|
||||
|
||||
auto dracoMesh = meshBuilder.Finalize();
|
||||
|
||||
if (!dracoMesh) {
|
||||
handleWarning("Failed to finalize the baking of a draco Geometry node");
|
||||
return false;
|
||||
}
|
||||
|
||||
// we need to modify unique attribute IDs for custom attributes
|
||||
// so the attributes are easily retrievable on the other side
|
||||
if (hasPerFaceMaterials) {
|
||||
dracoMesh->attribute(faceMaterialAttributeID)->set_unique_id(DRACO_ATTRIBUTE_MATERIAL_ID);
|
||||
}
|
||||
|
||||
if (hasTexCoords1) {
|
||||
dracoMesh->attribute(texCoords1AttributeID)->set_unique_id(DRACO_ATTRIBUTE_TEX_COORD_1);
|
||||
}
|
||||
|
||||
if (needsOriginalIndices) {
|
||||
dracoMesh->attribute(originalIndexAttributeID)->set_unique_id(DRACO_ATTRIBUTE_ORIGINAL_INDEX);
|
||||
}
|
||||
|
||||
draco::Encoder encoder;
|
||||
|
||||
encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 14);
|
||||
encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, 12);
|
||||
encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 10);
|
||||
encoder.SetSpeedOptions(0, 5);
|
||||
|
||||
draco::EncoderBuffer buffer;
|
||||
encoder.EncodeMeshToBuffer(*dracoMesh, &buffer);
|
||||
|
||||
FBXNode dracoNode;
|
||||
dracoNode.name = "DracoMesh";
|
||||
auto value = QVariant::fromValue(QByteArray(buffer.data(), (int)buffer.size()));
|
||||
dracoNode.properties.append(value);
|
||||
|
||||
dracoMeshNode = dracoNode;
|
||||
// Mesh compression successful return true
|
||||
return true;
|
||||
}
|
||||
|
||||
QString ModelBaker::compressTexture(QString modelTextureFileName, image::TextureUsage::Type textureType) {
|
||||
|
||||
QFileInfo modelTextureFileInfo{ modelTextureFileName.replace("\\", "/") };
|
||||
|
||||
if (modelTextureFileInfo.suffix() == BAKED_TEXTURE_EXT.mid(1)) {
|
||||
// re-baking a model that already references baked textures
|
||||
// this is an error - return from here
|
||||
handleError("Cannot re-bake a file that already references compressed textures");
|
||||
return QString::null;
|
||||
}
|
||||
|
||||
if (!image::getSupportedFormats().contains(modelTextureFileInfo.suffix())) {
|
||||
// this is a texture format we don't bake, skip it
|
||||
handleWarning(modelTextureFileName + " is not a bakeable texture format");
|
||||
return QString::null;
|
||||
}
|
||||
|
||||
// make sure this texture points to something and isn't one we've already re-mapped
|
||||
QString textureChild { QString::null };
|
||||
if (!modelTextureFileInfo.filePath().isEmpty()) {
|
||||
// check if this was an embedded texture that we already have in-memory content for
|
||||
QByteArray textureContent;
|
||||
|
||||
// figure out the URL to this texture, embedded or external
|
||||
if (!modelTextureFileInfo.filePath().isEmpty()) {
|
||||
textureContent = _textureContentMap.value(modelTextureFileName.toLocal8Bit());
|
||||
}
|
||||
auto urlToTexture = getTextureURL(modelTextureFileInfo, modelTextureFileName, !textureContent.isNull());
|
||||
|
||||
QString bakedTextureFileName;
|
||||
if (_remappedTexturePaths.contains(urlToTexture)) {
|
||||
bakedTextureFileName = _remappedTexturePaths[urlToTexture];
|
||||
} else {
|
||||
// construct the new baked texture file name and file path
|
||||
// ensuring that the baked texture will have a unique name
|
||||
// even if there was another texture with the same name at a different path
|
||||
bakedTextureFileName = createBakedTextureFileName(modelTextureFileInfo);
|
||||
_remappedTexturePaths[urlToTexture] = bakedTextureFileName;
|
||||
}
|
||||
|
||||
qCDebug(model_baking).noquote() << "Re-mapping" << modelTextureFileName
|
||||
<< "to" << bakedTextureFileName;
|
||||
|
||||
QString bakedTextureFilePath{
|
||||
_bakedOutputDir + "/" + bakedTextureFileName
|
||||
};
|
||||
|
||||
textureChild = bakedTextureFileName;
|
||||
|
||||
if (!_bakingTextures.contains(urlToTexture)) {
|
||||
_outputFiles.push_back(bakedTextureFilePath);
|
||||
|
||||
// bake this texture asynchronously
|
||||
bakeTexture(urlToTexture, textureType, _bakedOutputDir, bakedTextureFileName, textureContent);
|
||||
}
|
||||
}
|
||||
|
||||
return textureChild;
|
||||
}
|
||||
|
||||
void ModelBaker::bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType,
|
||||
const QDir& outputDir, const QString& bakedFilename, const QByteArray& textureContent) {
|
||||
|
||||
// start a bake for this texture and add it to our list to keep track of
|
||||
QSharedPointer<TextureBaker> bakingTexture{
|
||||
new TextureBaker(textureURL, textureType, outputDir, bakedFilename, textureContent),
|
||||
&TextureBaker::deleteLater
|
||||
};
|
||||
|
||||
// make sure we hear when the baking texture is done or aborted
|
||||
connect(bakingTexture.data(), &Baker::finished, this, &ModelBaker::handleBakedTexture);
|
||||
connect(bakingTexture.data(), &TextureBaker::aborted, this, &ModelBaker::handleAbortedTexture);
|
||||
|
||||
// keep a shared pointer to the baking texture
|
||||
_bakingTextures.insert(textureURL, bakingTexture);
|
||||
|
||||
// start baking the texture on one of our available worker threads
|
||||
bakingTexture->moveToThread(_textureThreadGetter());
|
||||
QMetaObject::invokeMethod(bakingTexture.data(), "bake");
|
||||
}
|
||||
|
||||
void ModelBaker::handleBakedTexture() {
|
||||
TextureBaker* bakedTexture = qobject_cast<TextureBaker*>(sender());
|
||||
qDebug() << "Handling baked texture" << bakedTexture->getTextureURL();
|
||||
|
||||
// make sure we haven't already run into errors, and that this is a valid texture
|
||||
if (bakedTexture) {
|
||||
if (!shouldStop()) {
|
||||
if (!bakedTexture->hasErrors()) {
|
||||
if (!_originalOutputDir.isEmpty()) {
|
||||
// we've been asked to make copies of the originals, so we need to make copies of this if it is a linked texture
|
||||
|
||||
// use the path to the texture being baked to determine if this was an embedded or a linked texture
|
||||
|
||||
// it is embeddded if the texure being baked was inside a folder with the name of the model
|
||||
// since that is the fake URL we provide when baking external textures
|
||||
|
||||
if (!_modelURL.isParentOf(bakedTexture->getTextureURL())) {
|
||||
// for linked textures we want to save a copy of original texture beside the original model
|
||||
|
||||
qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL();
|
||||
|
||||
// check if we have a relative path to use for the texture
|
||||
auto relativeTexturePath = texturePathRelativeToModel(_modelURL, bakedTexture->getTextureURL());
|
||||
|
||||
QFile originalTextureFile{
|
||||
_originalOutputDir + "/" + relativeTexturePath + bakedTexture->getTextureURL().fileName()
|
||||
};
|
||||
|
||||
if (relativeTexturePath.length() > 0) {
|
||||
// make the folders needed by the relative path
|
||||
}
|
||||
|
||||
if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) {
|
||||
qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName()
|
||||
<< "for" << _modelURL;
|
||||
} else {
|
||||
handleError("Could not save original external texture " + originalTextureFile.fileName()
|
||||
+ " for " + _modelURL.toString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// now that this texture has been baked and handled, we can remove that TextureBaker from our hash
|
||||
_bakingTextures.remove(bakedTexture->getTextureURL());
|
||||
|
||||
checkIfTexturesFinished();
|
||||
} else {
|
||||
// there was an error baking this texture - add it to our list of errors
|
||||
_errorList.append(bakedTexture->getErrors());
|
||||
|
||||
// we don't emit finished yet so that the other textures can finish baking first
|
||||
_pendingErrorEmission = true;
|
||||
|
||||
// now that this texture has been baked, even though it failed, we can remove that TextureBaker from our list
|
||||
_bakingTextures.remove(bakedTexture->getTextureURL());
|
||||
|
||||
// abort any other ongoing texture bakes since we know we'll end up failing
|
||||
for (auto& bakingTexture : _bakingTextures) {
|
||||
bakingTexture->abort();
|
||||
}
|
||||
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
} else {
|
||||
// we have errors to attend to, so we don't do extra processing for this texture
|
||||
// but we do need to remove that TextureBaker from our list
|
||||
// and then check if we're done with all textures
|
||||
_bakingTextures.remove(bakedTexture->getTextureURL());
|
||||
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::handleAbortedTexture() {
|
||||
// grab the texture bake that was aborted and remove it from our hash since we don't need to track it anymore
|
||||
TextureBaker* bakedTexture = qobject_cast<TextureBaker*>(sender());
|
||||
|
||||
qDebug() << "Texture aborted: " << bakedTexture->getTextureURL();
|
||||
|
||||
if (bakedTexture) {
|
||||
_bakingTextures.remove(bakedTexture->getTextureURL());
|
||||
}
|
||||
|
||||
// since a texture we were baking aborted, our status is also aborted
|
||||
_shouldAbort.store(true);
|
||||
|
||||
// abort any other ongoing texture bakes since we know we'll end up failing
|
||||
for (auto& bakingTexture : _bakingTextures) {
|
||||
bakingTexture->abort();
|
||||
}
|
||||
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
|
||||
QUrl ModelBaker::getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded) {
|
||||
QUrl urlToTexture;
|
||||
|
||||
// use QFileInfo to easily split up the existing texture filename into its components
|
||||
auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/"));
|
||||
|
||||
if (isEmbedded) {
|
||||
urlToTexture = _modelURL.toString() + "/" + apparentRelativePath.filePath();
|
||||
} else {
|
||||
if (textureFileInfo.exists() && textureFileInfo.isFile()) {
|
||||
// set the texture URL to the local texture that we have confirmed exists
|
||||
urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath());
|
||||
} else {
|
||||
// external texture that we'll need to download or find
|
||||
|
||||
// this is a relative file path which will require different handling
|
||||
// depending on the location of the original model
|
||||
if (_modelURL.isLocalFile() && apparentRelativePath.exists() && apparentRelativePath.isFile()) {
|
||||
// the absolute path we ran into for the texture in the model exists on this machine
|
||||
// so use that file
|
||||
urlToTexture = QUrl::fromLocalFile(apparentRelativePath.absoluteFilePath());
|
||||
} else {
|
||||
// we didn't find the texture on this machine at the absolute path
|
||||
// so assume that it is right beside the model to match the behaviour of interface
|
||||
urlToTexture = _modelURL.resolved(apparentRelativePath.fileName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return urlToTexture;
|
||||
}
|
||||
|
||||
QString ModelBaker::texturePathRelativeToModel(QUrl modelURL, QUrl textureURL) {
|
||||
auto modelPath = modelURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
auto texturePath = textureURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
|
||||
if (texturePath.startsWith(modelPath)) {
|
||||
// texture path is a child of the model path, return the texture path without the model path
|
||||
return texturePath.mid(modelPath.length());
|
||||
} else {
|
||||
// the texture path was not a child of the model path, return the empty string
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::checkIfTexturesFinished() {
|
||||
// check if we're done everything we need to do for this model
|
||||
// and emit our finished signal if we're done
|
||||
|
||||
if (_bakingTextures.isEmpty()) {
|
||||
if (shouldStop()) {
|
||||
// if we're checking for completion but we have errors
|
||||
// that means one or more of our texture baking operations failed
|
||||
|
||||
if (_pendingErrorEmission) {
|
||||
setIsFinished(true);
|
||||
}
|
||||
|
||||
return;
|
||||
} else {
|
||||
qCDebug(model_baking) << "Finished baking, emitting finished" << _modelURL;
|
||||
|
||||
setIsFinished(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString ModelBaker::createBakedTextureFileName(const QFileInfo& textureFileInfo) {
|
||||
// first make sure we have a unique base name for this texture
|
||||
// in case another texture referenced by this model has the same base name
|
||||
auto& nameMatches = _textureNameMatchCount[textureFileInfo.baseName()];
|
||||
|
||||
QString bakedTextureFileName{ textureFileInfo.completeBaseName() };
|
||||
|
||||
if (nameMatches > 0) {
|
||||
// there are already nameMatches texture with this name
|
||||
// append - and that number to our baked texture file name so that it is unique
|
||||
bakedTextureFileName += "-" + QString::number(nameMatches);
|
||||
}
|
||||
|
||||
bakedTextureFileName += BAKED_TEXTURE_EXT;
|
||||
|
||||
// increment the number of name matches
|
||||
++nameMatches;
|
||||
|
||||
return bakedTextureFileName;
|
||||
}
|
||||
|
||||
void ModelBaker::setWasAborted(bool wasAborted) {
|
||||
if (wasAborted != _wasAborted.load()) {
|
||||
Baker::setWasAborted(wasAborted);
|
||||
|
||||
if (wasAborted) {
|
||||
qCDebug(model_baking) << "Aborted baking" << _modelURL;
|
||||
}
|
||||
}
|
||||
}
|
79
libraries/baking/src/ModelBaker.h
Normal file
79
libraries/baking/src/ModelBaker.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
//
|
||||
// ModelBaker.h
|
||||
// libraries/baking/src
|
||||
//
|
||||
// Created by Utkarsh Gautam on 9/29/17.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_ModelBaker_h
|
||||
#define hifi_ModelBaker_h
|
||||
|
||||
#include <QtCore/QFutureSynchronizer>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include "Baker.h"
|
||||
#include "TextureBaker.h"
|
||||
|
||||
#include "ModelBakingLoggingCategory.h"
|
||||
|
||||
#include <gpu/Texture.h>
|
||||
|
||||
#include <FBX.h>
|
||||
|
||||
using TextureBakerThreadGetter = std::function<QThread*()>;
|
||||
using GetMaterialIDCallback = std::function <int(int)>;
|
||||
|
||||
class ModelBaker : public Baker {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "");
|
||||
virtual ~ModelBaker();
|
||||
|
||||
bool compressMesh(FBXMesh& mesh, bool hasDeformers, FBXNode& dracoMeshNode, GetMaterialIDCallback materialIDCallback = nullptr);
|
||||
QString compressTexture(QString textureFileName, image::TextureUsage::Type = image::TextureUsage::Type::DEFAULT_TEXTURE);
|
||||
virtual void setWasAborted(bool wasAborted) override;
|
||||
|
||||
QUrl getModelURL() const { return _modelURL; }
|
||||
QString getBakedModelFilePath() const { return _bakedModelFilePath; }
|
||||
|
||||
public slots:
|
||||
virtual void abort() override;
|
||||
|
||||
protected:
|
||||
void checkIfTexturesFinished();
|
||||
|
||||
QHash<QByteArray, QByteArray> _textureContentMap;
|
||||
QUrl _modelURL;
|
||||
QString _bakedOutputDir;
|
||||
QString _originalOutputDir;
|
||||
QString _bakedModelFilePath;
|
||||
QDir _modelTempDir;
|
||||
QString _originalModelFilePath;
|
||||
|
||||
private slots:
|
||||
void handleBakedTexture();
|
||||
void handleAbortedTexture();
|
||||
|
||||
private:
|
||||
QString createBakedTextureFileName(const QFileInfo & textureFileInfo);
|
||||
QUrl getTextureURL(const QFileInfo& textureFileInfo, QString relativeFileName, bool isEmbedded = false);
|
||||
void bakeTexture(const QUrl & textureURL, image::TextureUsage::Type textureType, const QDir & outputDir,
|
||||
const QString & bakedFilename, const QByteArray & textureContent);
|
||||
QString texturePathRelativeToModel(QUrl modelURL, QUrl textureURL);
|
||||
|
||||
TextureBakerThreadGetter _textureThreadGetter;
|
||||
QMultiHash<QUrl, QSharedPointer<TextureBaker>> _bakingTextures;
|
||||
QHash<QString, int> _textureNameMatchCount;
|
||||
QHash<QUrl, QString> _remappedTexturePaths;
|
||||
bool _pendingErrorEmission{ false };
|
||||
};
|
||||
|
||||
#endif // hifi_ModelBaker_h
|
404
libraries/baking/src/OBJBaker.cpp
Normal file
404
libraries/baking/src/OBJBaker.cpp
Normal file
|
@ -0,0 +1,404 @@
|
|||
//
|
||||
// OBJBaker.cpp
|
||||
// libraries/baking/src
|
||||
//
|
||||
// Created by Utkarsh Gautam on 9/29/17.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <PathUtils.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
|
||||
#include "OBJBaker.h"
|
||||
#include "OBJReader.h"
|
||||
#include "FBXWriter.h"
|
||||
|
||||
const double UNIT_SCALE_FACTOR = 100.0;
|
||||
const QByteArray PROPERTIES70_NODE_NAME = "Properties70";
|
||||
const QByteArray P_NODE_NAME = "P";
|
||||
const QByteArray C_NODE_NAME = "C";
|
||||
const QByteArray FBX_HEADER_EXTENSION = "FBXHeaderExtension";
|
||||
const QByteArray GLOBAL_SETTINGS_NODE_NAME = "GlobalSettings";
|
||||
const QByteArray OBJECTS_NODE_NAME = "Objects";
|
||||
const QByteArray GEOMETRY_NODE_NAME = "Geometry";
|
||||
const QByteArray MODEL_NODE_NAME = "Model";
|
||||
const QByteArray MATERIAL_NODE_NAME = "Material";
|
||||
const QByteArray TEXTURE_NODE_NAME = "Texture";
|
||||
const QByteArray TEXTURENAME_NODE_NAME = "TextureName";
|
||||
const QByteArray RELATIVEFILENAME_NODE_NAME = "RelativeFilename";
|
||||
const QByteArray CONNECTIONS_NODE_NAME = "Connections";
|
||||
const QByteArray CONNECTIONS_NODE_PROPERTY = "OO";
|
||||
const QByteArray CONNECTIONS_NODE_PROPERTY_1 = "OP";
|
||||
const QByteArray MESH = "Mesh";
|
||||
|
||||
void OBJBaker::bake() {
|
||||
qDebug() << "OBJBaker" << _modelURL << "bake starting";
|
||||
|
||||
// trigger bakeOBJ once OBJ is loaded
|
||||
connect(this, &OBJBaker::OBJLoaded, this, &OBJBaker::bakeOBJ);
|
||||
|
||||
// make a local copy of the OBJ
|
||||
loadOBJ();
|
||||
}
|
||||
|
||||
void OBJBaker::loadOBJ() {
|
||||
if (!QDir().mkpath(_bakedOutputDir)) {
|
||||
handleError("Failed to create baked OBJ output folder " + _bakedOutputDir);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!QDir().mkpath(_originalOutputDir)) {
|
||||
handleError("Failed to create original OBJ output folder " + _originalOutputDir);
|
||||
return;
|
||||
}
|
||||
|
||||
// check if the OBJ is local or it needs to be downloaded
|
||||
if (_modelURL.isLocalFile()) {
|
||||
// loading the local OBJ
|
||||
QFile localOBJ { _modelURL.toLocalFile() };
|
||||
|
||||
qDebug() << "Local file url: " << _modelURL << _modelURL.toString() << _modelURL.toLocalFile() << ", copying to: " << _originalModelFilePath;
|
||||
|
||||
if (!localOBJ.exists()) {
|
||||
handleError("Could not find " + _modelURL.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
// make a copy in the output folder
|
||||
if (!_originalOutputDir.isEmpty()) {
|
||||
qDebug() << "Copying to: " << _originalOutputDir << "/" << _modelURL.fileName();
|
||||
localOBJ.copy(_originalOutputDir + "/" + _modelURL.fileName());
|
||||
}
|
||||
|
||||
localOBJ.copy(_originalModelFilePath);
|
||||
|
||||
// local OBJ is loaded emit signal to trigger its baking
|
||||
emit OBJLoaded();
|
||||
} else {
|
||||
// OBJ is remote, start download
|
||||
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
||||
QNetworkRequest networkRequest;
|
||||
|
||||
// setup the request to follow re-directs and always hit the network
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
|
||||
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
networkRequest.setUrl(_modelURL);
|
||||
|
||||
qCDebug(model_baking) << "Downloading" << _modelURL;
|
||||
auto networkReply = networkAccessManager.get(networkRequest);
|
||||
|
||||
connect(networkReply, &QNetworkReply::finished, this, &OBJBaker::handleOBJNetworkReply);
|
||||
}
|
||||
}
|
||||
|
||||
void OBJBaker::handleOBJNetworkReply() {
|
||||
auto requestReply = qobject_cast<QNetworkReply*>(sender());
|
||||
|
||||
if (requestReply->error() == QNetworkReply::NoError) {
|
||||
qCDebug(model_baking) << "Downloaded" << _modelURL;
|
||||
|
||||
// grab the contents of the reply and make a copy in the output folder
|
||||
QFile copyOfOriginal(_originalModelFilePath);
|
||||
|
||||
qDebug(model_baking) << "Writing copy of original obj to" << _originalModelFilePath << copyOfOriginal.fileName();
|
||||
|
||||
if (!copyOfOriginal.open(QIODevice::WriteOnly)) {
|
||||
// add an error to the error list for this obj stating that a duplicate of the original obj could not be made
|
||||
handleError("Could not create copy of " + _modelURL.toString() + " (Failed to open " + _originalModelFilePath + ")");
|
||||
return;
|
||||
}
|
||||
if (copyOfOriginal.write(requestReply->readAll()) == -1) {
|
||||
handleError("Could not create copy of " + _modelURL.toString() + " (Failed to write)");
|
||||
return;
|
||||
}
|
||||
|
||||
// close that file now that we are done writing to it
|
||||
copyOfOriginal.close();
|
||||
|
||||
if (!_originalOutputDir.isEmpty()) {
|
||||
copyOfOriginal.copy(_originalOutputDir + "/" + _modelURL.fileName());
|
||||
}
|
||||
|
||||
// remote OBJ is loaded emit signal to trigger its baking
|
||||
emit OBJLoaded();
|
||||
} else {
|
||||
// add an error to our list stating that the OBJ could not be downloaded
|
||||
handleError("Failed to download " + _modelURL.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void OBJBaker::bakeOBJ() {
|
||||
// Read the OBJ file
|
||||
QFile objFile(_originalModelFilePath);
|
||||
if (!objFile.open(QIODevice::ReadOnly)) {
|
||||
handleError("Error opening " + _originalModelFilePath + " for reading");
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray objData = objFile.readAll();
|
||||
|
||||
bool combineParts = true; // set true so that OBJReader reads material info from material library
|
||||
OBJReader reader;
|
||||
auto geometry = reader.readOBJ(objData, QVariantHash(), combineParts, _modelURL);
|
||||
|
||||
// Write OBJ Data as FBX tree nodes
|
||||
FBXNode rootNode;
|
||||
createFBXNodeTree(rootNode, *geometry);
|
||||
|
||||
// Serialize the resultant FBX tree
|
||||
auto encodedFBX = FBXWriter::encodeFBX(rootNode);
|
||||
|
||||
// Export as baked FBX
|
||||
auto fileName = _modelURL.fileName();
|
||||
auto baseName = fileName.left(fileName.lastIndexOf('.'));
|
||||
auto bakedFilename = baseName + ".baked.fbx";
|
||||
|
||||
_bakedModelFilePath = _bakedOutputDir + "/" + bakedFilename;
|
||||
|
||||
QFile bakedFile;
|
||||
bakedFile.setFileName(_bakedModelFilePath);
|
||||
if (!bakedFile.open(QIODevice::WriteOnly)) {
|
||||
handleError("Error opening " + _bakedModelFilePath + " for writing");
|
||||
return;
|
||||
}
|
||||
|
||||
bakedFile.write(encodedFBX);
|
||||
|
||||
// Export successful
|
||||
_outputFiles.push_back(_bakedModelFilePath);
|
||||
qCDebug(model_baking) << "Exported" << _modelURL << "to" << _bakedModelFilePath;
|
||||
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
|
||||
void OBJBaker::createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry) {
|
||||
// Generating FBX Header Node
|
||||
FBXNode headerNode;
|
||||
headerNode.name = FBX_HEADER_EXTENSION;
|
||||
|
||||
// Generating global settings node
|
||||
// Required for Unit Scale Factor
|
||||
FBXNode globalSettingsNode;
|
||||
globalSettingsNode.name = GLOBAL_SETTINGS_NODE_NAME;
|
||||
|
||||
// Setting the tree hierarchy: GlobalSettings -> Properties70 -> P -> Properties
|
||||
FBXNode properties70Node;
|
||||
properties70Node.name = PROPERTIES70_NODE_NAME;
|
||||
|
||||
FBXNode pNode;
|
||||
{
|
||||
pNode.name = P_NODE_NAME;
|
||||
pNode.properties.append({
|
||||
"UnitScaleFactor", "double", "Number", "",
|
||||
UNIT_SCALE_FACTOR
|
||||
});
|
||||
}
|
||||
|
||||
properties70Node.children = { pNode };
|
||||
globalSettingsNode.children = { properties70Node };
|
||||
|
||||
// Generating Object node
|
||||
_objectNode.name = OBJECTS_NODE_NAME;
|
||||
|
||||
// Generating Object node's child - Geometry node
|
||||
FBXNode geometryNode;
|
||||
geometryNode.name = GEOMETRY_NODE_NAME;
|
||||
{
|
||||
_geometryID = nextNodeID();
|
||||
geometryNode.properties = {
|
||||
_geometryID,
|
||||
GEOMETRY_NODE_NAME,
|
||||
MESH
|
||||
};
|
||||
}
|
||||
|
||||
// Compress the mesh information and store in dracoNode
|
||||
bool hasDeformers = false; // No concept of deformers for an OBJ
|
||||
FBXNode dracoNode;
|
||||
compressMesh(geometry.meshes[0], hasDeformers, dracoNode);
|
||||
geometryNode.children.append(dracoNode);
|
||||
|
||||
// Generating Object node's child - Model node
|
||||
FBXNode modelNode;
|
||||
modelNode.name = MODEL_NODE_NAME;
|
||||
{
|
||||
_modelID = nextNodeID();
|
||||
modelNode.properties = { _modelID, MODEL_NODE_NAME, MESH };
|
||||
}
|
||||
|
||||
_objectNode.children = { geometryNode, modelNode };
|
||||
|
||||
// Generating Objects node's child - Material node
|
||||
auto& meshParts = geometry.meshes[0].parts;
|
||||
for (auto& meshPart : meshParts) {
|
||||
FBXNode materialNode;
|
||||
materialNode.name = MATERIAL_NODE_NAME;
|
||||
if (geometry.materials.size() == 1) {
|
||||
// case when no material information is provided, OBJReader considers it as a single default material
|
||||
for (auto& materialID : geometry.materials.keys()) {
|
||||
setMaterialNodeProperties(materialNode, materialID, geometry);
|
||||
}
|
||||
} else {
|
||||
setMaterialNodeProperties(materialNode, meshPart.materialID, geometry);
|
||||
}
|
||||
|
||||
_objectNode.children.append(materialNode);
|
||||
}
|
||||
|
||||
// Generating Texture Node
|
||||
// iterate through mesh parts and process the associated textures
|
||||
auto size = meshParts.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
QString material = meshParts[i].materialID;
|
||||
FBXMaterial currentMaterial = geometry.materials[material];
|
||||
if (!currentMaterial.albedoTexture.filename.isEmpty() || !currentMaterial.specularTexture.filename.isEmpty()) {
|
||||
_textureID = nextNodeID();
|
||||
_mapTextureMaterial.emplace_back(_textureID, i);
|
||||
|
||||
FBXNode textureNode;
|
||||
{
|
||||
textureNode.name = TEXTURE_NODE_NAME;
|
||||
textureNode.properties = { _textureID };
|
||||
}
|
||||
|
||||
// Texture node child - TextureName node
|
||||
FBXNode textureNameNode;
|
||||
{
|
||||
textureNameNode.name = TEXTURENAME_NODE_NAME;
|
||||
QByteArray propertyString = (!currentMaterial.albedoTexture.filename.isEmpty()) ? "Kd" : "Ka";
|
||||
textureNameNode.properties = { propertyString };
|
||||
}
|
||||
|
||||
// Texture node child - Relative Filename node
|
||||
FBXNode relativeFilenameNode;
|
||||
{
|
||||
relativeFilenameNode.name = RELATIVEFILENAME_NODE_NAME;
|
||||
}
|
||||
|
||||
QByteArray textureFileName = (!currentMaterial.albedoTexture.filename.isEmpty()) ? currentMaterial.albedoTexture.filename : currentMaterial.specularTexture.filename;
|
||||
|
||||
auto textureType = (!currentMaterial.albedoTexture.filename.isEmpty()) ? image::TextureUsage::Type::ALBEDO_TEXTURE : image::TextureUsage::Type::SPECULAR_TEXTURE;
|
||||
|
||||
// Compress the texture using ModelBaker::compressTexture() and store compressed file's name in the node
|
||||
auto textureFile = compressTexture(textureFileName, textureType);
|
||||
if (textureFile.isNull()) {
|
||||
// Baking failed return
|
||||
handleError("Failed to compress texture: " + textureFileName);
|
||||
return;
|
||||
}
|
||||
relativeFilenameNode.properties = { textureFile };
|
||||
|
||||
textureNode.children = { textureNameNode, relativeFilenameNode };
|
||||
|
||||
_objectNode.children.append(textureNode);
|
||||
}
|
||||
}
|
||||
|
||||
// Generating Connections node
|
||||
FBXNode connectionsNode;
|
||||
connectionsNode.name = CONNECTIONS_NODE_NAME;
|
||||
|
||||
// connect Geometry to Model
|
||||
FBXNode cNode;
|
||||
cNode.name = C_NODE_NAME;
|
||||
cNode.properties = { CONNECTIONS_NODE_PROPERTY, _geometryID, _modelID };
|
||||
connectionsNode.children = { cNode };
|
||||
|
||||
// connect all materials to model
|
||||
for (auto& materialID : _materialIDs) {
|
||||
FBXNode cNode;
|
||||
cNode.name = C_NODE_NAME;
|
||||
cNode.properties = { CONNECTIONS_NODE_PROPERTY, materialID, _modelID };
|
||||
connectionsNode.children.append(cNode);
|
||||
}
|
||||
|
||||
// Connect textures to materials
|
||||
for (const auto& texMat : _mapTextureMaterial) {
|
||||
FBXNode cAmbientNode;
|
||||
cAmbientNode.name = C_NODE_NAME;
|
||||
cAmbientNode.properties = {
|
||||
CONNECTIONS_NODE_PROPERTY_1,
|
||||
texMat.first,
|
||||
_materialIDs[texMat.second],
|
||||
"AmbientFactor"
|
||||
};
|
||||
connectionsNode.children.append(cAmbientNode);
|
||||
|
||||
FBXNode cDiffuseNode;
|
||||
cDiffuseNode.name = C_NODE_NAME;
|
||||
cDiffuseNode.properties = {
|
||||
CONNECTIONS_NODE_PROPERTY_1,
|
||||
texMat.first,
|
||||
_materialIDs[texMat.second],
|
||||
"DiffuseColor"
|
||||
};
|
||||
connectionsNode.children.append(cDiffuseNode);
|
||||
}
|
||||
|
||||
// Make all generated nodes children of rootNode
|
||||
rootNode.children = { globalSettingsNode, _objectNode, connectionsNode };
|
||||
}
|
||||
|
||||
// Set properties for material nodes
|
||||
void OBJBaker::setMaterialNodeProperties(FBXNode& materialNode, QString material, FBXGeometry& geometry) {
|
||||
auto materialID = nextNodeID();
|
||||
_materialIDs.push_back(materialID);
|
||||
materialNode.properties = { materialID, material, MESH };
|
||||
|
||||
FBXMaterial currentMaterial = geometry.materials[material];
|
||||
|
||||
// Setting the hierarchy: Material -> Properties70 -> P -> Properties
|
||||
FBXNode properties70Node;
|
||||
properties70Node.name = PROPERTIES70_NODE_NAME;
|
||||
|
||||
// Set diffuseColor
|
||||
FBXNode pNodeDiffuseColor;
|
||||
{
|
||||
pNodeDiffuseColor.name = P_NODE_NAME;
|
||||
pNodeDiffuseColor.properties.append({
|
||||
"DiffuseColor", "Color", "", "A",
|
||||
currentMaterial.diffuseColor[0], currentMaterial.diffuseColor[1], currentMaterial.diffuseColor[2]
|
||||
});
|
||||
}
|
||||
properties70Node.children.append(pNodeDiffuseColor);
|
||||
|
||||
// Set specularColor
|
||||
FBXNode pNodeSpecularColor;
|
||||
{
|
||||
pNodeSpecularColor.name = P_NODE_NAME;
|
||||
pNodeSpecularColor.properties.append({
|
||||
"SpecularColor", "Color", "", "A",
|
||||
currentMaterial.specularColor[0], currentMaterial.specularColor[1], currentMaterial.specularColor[2]
|
||||
});
|
||||
}
|
||||
properties70Node.children.append(pNodeSpecularColor);
|
||||
|
||||
// Set Shininess
|
||||
FBXNode pNodeShininess;
|
||||
{
|
||||
pNodeShininess.name = P_NODE_NAME;
|
||||
pNodeShininess.properties.append({
|
||||
"Shininess", "Number", "", "A",
|
||||
currentMaterial.shininess
|
||||
});
|
||||
}
|
||||
properties70Node.children.append(pNodeShininess);
|
||||
|
||||
// Set Opacity
|
||||
FBXNode pNodeOpacity;
|
||||
{
|
||||
pNodeOpacity.name = P_NODE_NAME;
|
||||
pNodeOpacity.properties.append({
|
||||
"Opacity", "Number", "", "A",
|
||||
currentMaterial.opacity
|
||||
});
|
||||
}
|
||||
properties70Node.children.append(pNodeOpacity);
|
||||
|
||||
materialNode.children.append(properties70Node);
|
||||
}
|
54
libraries/baking/src/OBJBaker.h
Normal file
54
libraries/baking/src/OBJBaker.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// OBJBaker.h
|
||||
// libraries/baking/src
|
||||
//
|
||||
// Created by Utkarsh Gautam on 9/29/17.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_OBJBaker_h
|
||||
#define hifi_OBJBaker_h
|
||||
|
||||
#include "Baker.h"
|
||||
#include "TextureBaker.h"
|
||||
#include "ModelBaker.h"
|
||||
|
||||
#include "ModelBakingLoggingCategory.h"
|
||||
|
||||
using TextureBakerThreadGetter = std::function<QThread*()>;
|
||||
|
||||
using NodeID = qlonglong;
|
||||
|
||||
class OBJBaker : public ModelBaker {
|
||||
Q_OBJECT
|
||||
public:
|
||||
using ModelBaker::ModelBaker;
|
||||
|
||||
public slots:
|
||||
virtual void bake() override;
|
||||
|
||||
signals:
|
||||
void OBJLoaded();
|
||||
|
||||
private slots:
|
||||
void bakeOBJ();
|
||||
void handleOBJNetworkReply();
|
||||
|
||||
private:
|
||||
void loadOBJ();
|
||||
void createFBXNodeTree(FBXNode& rootNode, FBXGeometry& geometry);
|
||||
void setMaterialNodeProperties(FBXNode& materialNode, QString material, FBXGeometry& geometry);
|
||||
NodeID nextNodeID() { return _nodeID++; }
|
||||
|
||||
NodeID _nodeID { 0 };
|
||||
NodeID _geometryID;
|
||||
NodeID _modelID;
|
||||
std::vector<NodeID> _materialIDs;
|
||||
NodeID _textureID;
|
||||
std::vector<std::pair<NodeID, int>> _mapTextureMaterial;
|
||||
FBXNode _objectNode;
|
||||
};
|
||||
#endif // hifi_OBJBaker_h
|
|
@ -21,10 +21,6 @@ glm::uvec2 NullDisplayPlugin::getRecommendedRenderSize() const {
|
|||
return glm::uvec2(100, 100);
|
||||
}
|
||||
|
||||
bool NullDisplayPlugin::hasFocus() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void NullDisplayPlugin::submitFrame(const gpu::FramePointer& frame) {
|
||||
if (frame) {
|
||||
_gpuContext->consumeFrameUpdates(frame);
|
||||
|
|
|
@ -16,7 +16,6 @@ public:
|
|||
grouping getGrouping() const override { return DEVELOPER; }
|
||||
|
||||
glm::uvec2 getRecommendedRenderSize() const override;
|
||||
bool hasFocus() const override;
|
||||
void submitFrame(const gpu::FramePointer& newFrame) override;
|
||||
QImage getScreenshot(float aspectRatio = 0.0f) const override;
|
||||
QImage getSecondaryCameraScreenshot() const override;
|
||||
|
|
|
@ -831,11 +831,6 @@ glm::uvec2 OpenGLDisplayPlugin::getSurfaceSize() const {
|
|||
return result;
|
||||
}
|
||||
|
||||
bool OpenGLDisplayPlugin::hasFocus() const {
|
||||
auto window = _container->getPrimaryWidget();
|
||||
return window ? window->hasFocus() : false;
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::assertNotPresentThread() const {
|
||||
Q_ASSERT(QThread::currentThread() != _presentThread);
|
||||
}
|
||||
|
|
|
@ -98,8 +98,6 @@ protected:
|
|||
virtual void compositePointer();
|
||||
virtual void compositeExtra() {};
|
||||
|
||||
virtual bool hasFocus() const override;
|
||||
|
||||
// These functions must only be called on the presentation thread
|
||||
virtual void customizeContext();
|
||||
virtual void uncustomizeContext();
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include "RenderableMaterialEntityItem.h"
|
||||
|
||||
#include "RenderPipelines.h"
|
||||
#include "GeometryCache.h"
|
||||
|
||||
using namespace render;
|
||||
using namespace render::entities;
|
||||
|
@ -90,138 +91,6 @@ ShapeKey MaterialEntityRenderer::getShapeKey() {
|
|||
return builder.build();
|
||||
}
|
||||
|
||||
glm::vec3 MaterialEntityRenderer::getVertexPos(float phi, float theta) {
|
||||
return glm::vec3(glm::sin(theta) * glm::cos(phi), glm::cos(theta), glm::sin(theta) * glm::sin(phi));
|
||||
}
|
||||
|
||||
glm::vec3 MaterialEntityRenderer::getTangent(float phi, float theta) {
|
||||
return glm::vec3(-glm::cos(theta) * glm::cos(phi), glm::sin(theta), -glm::cos(theta) * glm::sin(phi));
|
||||
}
|
||||
|
||||
void MaterialEntityRenderer::addVertex(std::vector<float>& buffer, const glm::vec3& pos, const glm::vec3& tan, const glm::vec2 uv) {
|
||||
buffer.push_back(pos.x); buffer.push_back(pos.y); buffer.push_back(pos.z);
|
||||
buffer.push_back(tan.x); buffer.push_back(tan.y); buffer.push_back(tan.z);
|
||||
buffer.push_back(uv.x); buffer.push_back(uv.y);
|
||||
}
|
||||
|
||||
void MaterialEntityRenderer::addTriangleFan(std::vector<float>& buffer, int stack, int step) {
|
||||
float v1 = ((float)stack) / STACKS;
|
||||
float theta1 = v1 * (float)M_PI;
|
||||
glm::vec3 tip = getVertexPos(0, theta1);
|
||||
float v2 = ((float)(stack + step)) / STACKS;
|
||||
float theta2 = v2 * (float)M_PI;
|
||||
for (int i = 0; i < SLICES; i++) {
|
||||
float u1 = ((float)i) / SLICES;
|
||||
float u2 = ((float)(i + step)) / SLICES;
|
||||
float phi1 = u1 * M_PI_TIMES_2;
|
||||
float phi2 = u2 * M_PI_TIMES_2;
|
||||
/* (flipped for negative step)
|
||||
p1
|
||||
/ \
|
||||
/ \
|
||||
/ \
|
||||
p3 ------ p2
|
||||
*/
|
||||
|
||||
glm::vec3 pos2 = getVertexPos(phi2, theta2);
|
||||
glm::vec3 pos3 = getVertexPos(phi1, theta2);
|
||||
|
||||
glm::vec3 tan1 = getTangent(0, theta1);
|
||||
glm::vec3 tan2 = getTangent(phi2, theta2);
|
||||
glm::vec3 tan3 = getTangent(phi1, theta2);
|
||||
|
||||
glm::vec2 uv1 = glm::vec2((u1 + u2) / 2.0f, v1);
|
||||
glm::vec2 uv2 = glm::vec2(u2, v2);
|
||||
glm::vec2 uv3 = glm::vec2(u1, v2);
|
||||
|
||||
addVertex(buffer, tip, tan1, uv1);
|
||||
addVertex(buffer, pos2, tan2, uv2);
|
||||
addVertex(buffer, pos3, tan3, uv3);
|
||||
|
||||
_numVertices += 3;
|
||||
}
|
||||
}
|
||||
|
||||
int MaterialEntityRenderer::_numVertices = 0;
|
||||
std::shared_ptr<gpu::Stream::Format> MaterialEntityRenderer::_streamFormat = nullptr;
|
||||
std::shared_ptr<gpu::BufferStream> MaterialEntityRenderer::_stream = nullptr;
|
||||
std::shared_ptr<gpu::Buffer> MaterialEntityRenderer::_verticesBuffer = nullptr;
|
||||
|
||||
void MaterialEntityRenderer::generateMesh() {
|
||||
_streamFormat = std::make_shared<gpu::Stream::Format>();
|
||||
_stream = std::make_shared<gpu::BufferStream>();
|
||||
_verticesBuffer = std::make_shared<gpu::Buffer>();
|
||||
|
||||
const int NUM_POS_COORDS = 3;
|
||||
const int NUM_TANGENT_COORDS = 3;
|
||||
const int VERTEX_TANGENT_OFFSET = NUM_POS_COORDS * sizeof(float);
|
||||
const int VERTEX_TEXCOORD_OFFSET = VERTEX_TANGENT_OFFSET + NUM_TANGENT_COORDS * sizeof(float);
|
||||
|
||||
_streamFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
|
||||
_streamFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
|
||||
_streamFormat->setAttribute(gpu::Stream::TANGENT, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), VERTEX_TANGENT_OFFSET);
|
||||
_streamFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), VERTEX_TEXCOORD_OFFSET);
|
||||
|
||||
_stream->addBuffer(_verticesBuffer, 0, _streamFormat->getChannels().at(0)._stride);
|
||||
|
||||
std::vector<float> vertexBuffer;
|
||||
|
||||
// Top
|
||||
addTriangleFan(vertexBuffer, 0, 1);
|
||||
|
||||
// Middle section
|
||||
for (int j = 1; j < STACKS - 1; j++) {
|
||||
float v1 = ((float)j) / STACKS;
|
||||
float v2 = ((float)(j + 1)) / STACKS;
|
||||
float theta1 = v1 * (float)M_PI;
|
||||
float theta2 = v2 * (float)M_PI;
|
||||
for (int i = 0; i < SLICES; i++) {
|
||||
float u1 = ((float)i) / SLICES;
|
||||
float u2 = ((float)(i + 1)) / SLICES;
|
||||
float phi1 = u1 * M_PI_TIMES_2;
|
||||
float phi2 = u2 * M_PI_TIMES_2;
|
||||
|
||||
/*
|
||||
p2 ---- p3
|
||||
| / |
|
||||
| / |
|
||||
| / |
|
||||
p1 ---- p4
|
||||
*/
|
||||
|
||||
glm::vec3 pos1 = getVertexPos(phi1, theta2);
|
||||
glm::vec3 pos2 = getVertexPos(phi1, theta1);
|
||||
glm::vec3 pos3 = getVertexPos(phi2, theta1);
|
||||
glm::vec3 pos4 = getVertexPos(phi2, theta2);
|
||||
|
||||
glm::vec3 tan1 = getTangent(phi1, theta2);
|
||||
glm::vec3 tan2 = getTangent(phi1, theta1);
|
||||
glm::vec3 tan3 = getTangent(phi2, theta1);
|
||||
glm::vec3 tan4 = getTangent(phi2, theta2);
|
||||
|
||||
glm::vec2 uv1 = glm::vec2(u1, v2);
|
||||
glm::vec2 uv2 = glm::vec2(u1, v1);
|
||||
glm::vec2 uv3 = glm::vec2(u2, v1);
|
||||
glm::vec2 uv4 = glm::vec2(u2, v2);
|
||||
|
||||
addVertex(vertexBuffer, pos1, tan1, uv1);
|
||||
addVertex(vertexBuffer, pos2, tan2, uv2);
|
||||
addVertex(vertexBuffer, pos3, tan3, uv3);
|
||||
|
||||
addVertex(vertexBuffer, pos3, tan3, uv3);
|
||||
addVertex(vertexBuffer, pos4, tan4, uv4);
|
||||
addVertex(vertexBuffer, pos1, tan1, uv1);
|
||||
|
||||
_numVertices += 6;
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom
|
||||
addTriangleFan(vertexBuffer, STACKS, -1);
|
||||
|
||||
_verticesBuffer->append(vertexBuffer.size() * sizeof(float), (gpu::Byte*) vertexBuffer.data());
|
||||
}
|
||||
|
||||
void MaterialEntityRenderer::doRender(RenderArgs* args) {
|
||||
PerformanceTimer perfTimer("RenderableMaterialEntityItem::render");
|
||||
Q_ASSERT(args->_batch);
|
||||
|
@ -252,14 +121,7 @@ void MaterialEntityRenderer::doRender(RenderArgs* args) {
|
|||
args->_details._materialSwitches++;
|
||||
|
||||
// Draw!
|
||||
if (_numVertices == 0) {
|
||||
generateMesh();
|
||||
}
|
||||
DependencyManager::get<GeometryCache>()->renderSphere(batch);
|
||||
|
||||
batch.setInputFormat(_streamFormat);
|
||||
batch.setInputStream(0, *_stream);
|
||||
batch.draw(gpu::TRIANGLES, _numVertices, 0);
|
||||
|
||||
const int NUM_VERTICES_PER_TRIANGLE = 3;
|
||||
args->_details._trianglesRendered += _numVertices / NUM_VERTICES_PER_TRIANGLE;
|
||||
args->_details._trianglesRendered += (int)DependencyManager::get<GeometryCache>()->getSphereTriangleCount();
|
||||
}
|
||||
|
|
|
@ -40,20 +40,6 @@ private:
|
|||
Transform _renderTransform;
|
||||
|
||||
std::shared_ptr<NetworkMaterial> _drawMaterial;
|
||||
|
||||
static int _numVertices;
|
||||
static std::shared_ptr<gpu::Stream::Format> _streamFormat;
|
||||
static std::shared_ptr<gpu::BufferStream> _stream;
|
||||
static std::shared_ptr<gpu::Buffer> _verticesBuffer;
|
||||
|
||||
void generateMesh();
|
||||
void addTriangleFan(std::vector<float>& buffer, int stack, int step);
|
||||
static glm::vec3 getVertexPos(float phi, float theta);
|
||||
static glm::vec3 getTangent(float phi, float theta);
|
||||
static void addVertex(std::vector<float>& buffer, const glm::vec3& pos, const glm::vec3& tan, const glm::vec2 uv);
|
||||
const int SLICES = 15;
|
||||
const int STACKS = 9;
|
||||
const float M_PI_TIMES_2 = 2.0f * (float)M_PI;
|
||||
};
|
||||
|
||||
} }
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
#include "render-utils/simple_vert.h"
|
||||
#include "render-utils/simple_frag.h"
|
||||
|
||||
#include "RenderPipelines.h"
|
||||
|
||||
//#define SHAPE_ENTITY_USE_FADE_EFFECT
|
||||
#ifdef SHAPE_ENTITY_USE_FADE_EFFECT
|
||||
#include <FadeEffect.h>
|
||||
|
@ -108,11 +110,94 @@ bool ShapeEntityRenderer::isTransparent() const {
|
|||
if (_procedural.isEnabled() && _procedural.isFading()) {
|
||||
return Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) < 1.0f;
|
||||
}
|
||||
|
||||
// return _entity->getLocalRenderAlpha() < 1.0f || Parent::isTransparent();
|
||||
|
||||
auto mat = _materials.find("0");
|
||||
if (mat != _materials.end()) {
|
||||
if (mat->second.top().material) {
|
||||
auto matKey = mat->second.top().material->getKey();
|
||||
if (matKey.isTranslucent()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Parent::isTransparent();
|
||||
}
|
||||
|
||||
ItemKey ShapeEntityRenderer::getKey() {
|
||||
ItemKey::Builder builder;
|
||||
builder.withTypeShape().withTypeMeta().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1);
|
||||
|
||||
withReadLock([&] {
|
||||
if (isTransparent()) {
|
||||
builder.withTransparent();
|
||||
}
|
||||
});
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
bool ShapeEntityRenderer::useMaterialPipeline() const {
|
||||
bool proceduralReady = resultWithReadLock<bool>([&] {
|
||||
return _procedural.isReady();
|
||||
});
|
||||
if (proceduralReady) {
|
||||
return false;
|
||||
}
|
||||
|
||||
graphics::MaterialKey drawMaterialKey;
|
||||
auto mat = _materials.find("0");
|
||||
if (mat != _materials.end() && mat->second.top().material) {
|
||||
drawMaterialKey = mat->second.top().material->getKey();
|
||||
}
|
||||
|
||||
if (drawMaterialKey.isEmissive() || drawMaterialKey.isUnlit() || drawMaterialKey.isMetallic() || drawMaterialKey.isScattering()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the material is using any map, we need to use a material ShapeKey
|
||||
for (int i = 0; i < graphics::Material::MapChannel::NUM_MAP_CHANNELS; i++) {
|
||||
if (drawMaterialKey.isMapChannel(graphics::Material::MapChannel(i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ShapeKey ShapeEntityRenderer::getShapeKey() {
|
||||
if (useMaterialPipeline()) {
|
||||
graphics::MaterialKey drawMaterialKey;
|
||||
if (_materials["0"].top().material) {
|
||||
drawMaterialKey = _materials["0"].top().material->getKey();
|
||||
}
|
||||
|
||||
bool isTranslucent = drawMaterialKey.isTranslucent();
|
||||
bool hasTangents = drawMaterialKey.isNormalMap();
|
||||
bool hasLightmap = drawMaterialKey.isLightmapMap();
|
||||
bool isUnlit = drawMaterialKey.isUnlit();
|
||||
|
||||
ShapeKey::Builder builder;
|
||||
builder.withMaterial();
|
||||
|
||||
if (isTranslucent) {
|
||||
builder.withTranslucent();
|
||||
}
|
||||
if (hasTangents) {
|
||||
builder.withTangents();
|
||||
}
|
||||
if (hasLightmap) {
|
||||
builder.withLightmap();
|
||||
}
|
||||
if (isUnlit) {
|
||||
builder.withUnlit();
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
} else {
|
||||
return Parent::getShapeKey();
|
||||
}
|
||||
}
|
||||
|
||||
void ShapeEntityRenderer::doRender(RenderArgs* args) {
|
||||
PerformanceTimer perfTimer("RenderableShapeEntityItem::render");
|
||||
Q_ASSERT(args->_batch);
|
||||
|
@ -149,7 +234,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
|
|||
} else {
|
||||
geometryCache->renderShape(batch, geometryShape, outColor);
|
||||
}
|
||||
} else {
|
||||
} else if (!useMaterialPipeline()) {
|
||||
// FIXME, support instanced multi-shape rendering using multidraw indirect
|
||||
outColor.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
|
||||
auto pipeline = outColor.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline();
|
||||
|
@ -158,6 +243,11 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) {
|
|||
} else {
|
||||
geometryCache->renderSolidShapeInstance(args, batch, geometryShape, outColor, pipeline);
|
||||
}
|
||||
} else {
|
||||
RenderPipelines::bindMaterial(mat, batch, args->_enableTexturing);
|
||||
args->_details._materialSwitches++;
|
||||
|
||||
geometryCache->renderShape(batch, geometryShape);
|
||||
}
|
||||
|
||||
const auto triCount = geometryCache->getShapeTriangleCount(geometryShape);
|
||||
|
|
|
@ -24,6 +24,10 @@ public:
|
|||
|
||||
virtual scriptable::ScriptableModelBase getScriptableModel() override;
|
||||
|
||||
protected:
|
||||
ItemKey getKey() override;
|
||||
ShapeKey getShapeKey() override;
|
||||
|
||||
private:
|
||||
virtual bool needsRenderUpdate() const override;
|
||||
virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override;
|
||||
|
@ -32,6 +36,8 @@ private:
|
|||
virtual void doRender(RenderArgs* args) override;
|
||||
virtual bool isTransparent() const override;
|
||||
|
||||
bool useMaterialPipeline() const;
|
||||
|
||||
Procedural _procedural;
|
||||
QString _lastUserData;
|
||||
Transform _renderTransform;
|
||||
|
|
|
@ -111,7 +111,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) {
|
|||
if (!_geometryID) {
|
||||
_geometryID = geometryCache->allocateID();
|
||||
}
|
||||
geometryCache->bindSimpleProgram(batch, false, transparent, false, false, false, false);
|
||||
geometryCache->bindSimpleProgram(batch, false, transparent, false, false, false);
|
||||
geometryCache->renderQuad(batch, minCorner, maxCorner, backgroundColor, _geometryID);
|
||||
|
||||
float scale = _lineHeight / _textRenderer->getFontSize();
|
||||
|
|
|
@ -46,6 +46,19 @@ static int YOUTUBE_MAX_FPS = 30;
|
|||
|
||||
static QTouchDevice _touchDevice;
|
||||
|
||||
WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& urlString) {
|
||||
if (urlString.isEmpty()) {
|
||||
return ContentType::NoContent;
|
||||
}
|
||||
|
||||
const QUrl url(urlString);
|
||||
if (url.scheme() == "http" || url.scheme() == "https" ||
|
||||
urlString.toLower().endsWith(".htm") || urlString.toLower().endsWith(".html")) {
|
||||
return ContentType::HtmlContent;
|
||||
}
|
||||
return ContentType::QmlContent;
|
||||
}
|
||||
|
||||
WebEntityRenderer::WebEntityRenderer(const EntityItemPointer& entity) : Parent(entity) {
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&]{
|
||||
|
@ -123,13 +136,45 @@ void WebEntityRenderer::onTimeout() {
|
|||
}
|
||||
|
||||
void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
|
||||
withWriteLock([&] {
|
||||
// This work must be done on the main thread
|
||||
if (!hasWebSurface()) {
|
||||
// If we couldn't create a new web surface, exit
|
||||
if (!buildWebSurface(entity)) {
|
||||
return;
|
||||
// If the content type has changed, or the old content type was QML, we need to
|
||||
// destroy the existing surface (because surfaces don't support changing the root
|
||||
// object, so subsequent loads of content just overlap the existing content
|
||||
bool urlChanged = false;
|
||||
{
|
||||
auto newSourceUrl = entity->getSourceUrl();
|
||||
auto newContentType = getContentType(newSourceUrl);
|
||||
auto currentContentType = ContentType::NoContent;
|
||||
withReadLock([&] {
|
||||
urlChanged = _lastSourceUrl != newSourceUrl;
|
||||
currentContentType = _contentType;
|
||||
});
|
||||
|
||||
if (urlChanged) {
|
||||
if (newContentType != ContentType::HtmlContent || currentContentType != ContentType::HtmlContent) {
|
||||
destroyWebSurface();
|
||||
}
|
||||
|
||||
withWriteLock([&] {
|
||||
_lastSourceUrl = newSourceUrl;
|
||||
_contentType = newContentType;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
withWriteLock([&] {
|
||||
if (_contentType == ContentType::NoContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This work must be done on the main thread
|
||||
// If we couldn't create a new web surface, exit
|
||||
if (!hasWebSurface() && !buildWebSurface(entity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (urlChanged) {
|
||||
_webSurface->getRootItem()->setProperty("url", _lastSourceUrl);
|
||||
}
|
||||
|
||||
if (_contextPosition != entity->getWorldPosition()) {
|
||||
|
@ -138,11 +183,6 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene
|
|||
_webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition));
|
||||
}
|
||||
|
||||
if (_lastSourceUrl != entity->getSourceUrl()) {
|
||||
_lastSourceUrl = entity->getSourceUrl();
|
||||
loadSourceURL();
|
||||
}
|
||||
|
||||
_lastDPI = entity->getDPI();
|
||||
_lastLocked = entity->getLocked();
|
||||
|
||||
|
@ -199,7 +239,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) {
|
|||
}
|
||||
|
||||
bool WebEntityRenderer::hasWebSurface() {
|
||||
return (bool)_webSurface;
|
||||
return (bool)_webSurface && _webSurface->getRootItem();
|
||||
}
|
||||
|
||||
bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) {
|
||||
|
@ -232,9 +272,6 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) {
|
|||
// Let us interact with the keyboard
|
||||
surfaceContext->setContextProperty("tabletInterface", DependencyManager::get<TabletScriptingInterface>().data());
|
||||
});
|
||||
_fadeStartTime = usecTimestampNow();
|
||||
loadSourceURL();
|
||||
_webSurface->resume();
|
||||
|
||||
// forward web events to EntityScriptingInterface
|
||||
auto entities = DependencyManager::get<EntityScriptingInterface>();
|
||||
|
@ -243,7 +280,30 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) {
|
|||
emit entities->webEventReceived(entityItemID, message);
|
||||
});
|
||||
|
||||
return true;
|
||||
if (_contentType == ContentType::HtmlContent) {
|
||||
// We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS.
|
||||
// FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the
|
||||
// web entity
|
||||
if (QUrl(_lastSourceUrl).host().endsWith("youtube.com", Qt::CaseInsensitive)) {
|
||||
_webSurface->setMaxFps(YOUTUBE_MAX_FPS);
|
||||
} else {
|
||||
_webSurface->setMaxFps(DEFAULT_MAX_FPS);
|
||||
}
|
||||
_webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) {
|
||||
item->setProperty("url", _lastSourceUrl);
|
||||
});
|
||||
} else if (_contentType == ContentType::QmlContent) {
|
||||
_webSurface->load(_lastSourceUrl, [this](QQmlContext* context, QObject* item) {
|
||||
if (item && item->objectName() == "tabletRoot") {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data());
|
||||
}
|
||||
});
|
||||
}
|
||||
_fadeStartTime = usecTimestampNow();
|
||||
_webSurface->resume();
|
||||
|
||||
return _webSurface->getRootItem();
|
||||
}
|
||||
|
||||
void WebEntityRenderer::destroyWebSurface() {
|
||||
|
@ -289,32 +349,6 @@ glm::vec2 WebEntityRenderer::getWindowSize(const TypedEntityPointer& entity) con
|
|||
return dims;
|
||||
}
|
||||
|
||||
void WebEntityRenderer::loadSourceURL() {
|
||||
const QUrl sourceUrl(_lastSourceUrl);
|
||||
if (sourceUrl.scheme() == "http" || sourceUrl.scheme() == "https" ||
|
||||
_lastSourceUrl.toLower().endsWith(".htm") || _lastSourceUrl.toLower().endsWith(".html")) {
|
||||
_contentType = htmlContent;
|
||||
|
||||
// We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS.
|
||||
if (sourceUrl.host().endsWith("youtube.com", Qt::CaseInsensitive)) {
|
||||
_webSurface->setMaxFps(YOUTUBE_MAX_FPS);
|
||||
} else {
|
||||
_webSurface->setMaxFps(DEFAULT_MAX_FPS);
|
||||
}
|
||||
|
||||
_webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) {
|
||||
item->setProperty("url", _lastSourceUrl);
|
||||
});
|
||||
} else {
|
||||
_contentType = qmlContent;
|
||||
_webSurface->load(_lastSourceUrl);
|
||||
if (_webSurface->getRootItem() && _webSurface->getRootItem()->objectName() == "tabletRoot") {
|
||||
auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
|
||||
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebEntityRenderer::hoverEnterEntity(const PointerEvent& event) {
|
||||
if (!_lastLocked && _webSurface) {
|
||||
PointerEvent webEvent = event;
|
||||
|
|
|
@ -47,15 +47,19 @@ private:
|
|||
bool buildWebSurface(const TypedEntityPointer& entity);
|
||||
void destroyWebSurface();
|
||||
bool hasWebSurface();
|
||||
void loadSourceURL();
|
||||
glm::vec2 getWindowSize(const TypedEntityPointer& entity) const;
|
||||
|
||||
|
||||
int _geometryId{ 0 };
|
||||
enum contentType {
|
||||
htmlContent,
|
||||
qmlContent
|
||||
enum class ContentType {
|
||||
NoContent,
|
||||
HtmlContent,
|
||||
QmlContent
|
||||
};
|
||||
contentType _contentType;
|
||||
|
||||
static ContentType getContentType(const QString& urlString);
|
||||
|
||||
ContentType _contentType{ ContentType::NoContent };
|
||||
QSharedPointer<OffscreenQmlSurface> _webSurface;
|
||||
glm::vec3 _contextPosition;
|
||||
gpu::TexturePointer _texture;
|
||||
|
|
|
@ -963,7 +963,11 @@ void EntityItem::setHref(QString value) {
|
|||
|
||||
// If the string has something and doesn't start with with "hifi://" it shouldn't be set
|
||||
// We allow the string to be empty, because that's the initial state of this property
|
||||
if ( !(value.toLower().startsWith("hifi://")) && !value.isEmpty()) {
|
||||
if (!value.isEmpty() &&
|
||||
!(value.toLower().startsWith("hifi://")) &&
|
||||
!(value.toLower().startsWith("file://"))
|
||||
// TODO: serverless-domains will eventually support http and https also
|
||||
) {
|
||||
return;
|
||||
}
|
||||
withWriteLock([&] {
|
||||
|
|
|
@ -33,7 +33,7 @@ public:
|
|||
// these can only be called from the OctreeSendThread for the given Node
|
||||
void insertSentFilteredEntity(const QUuid& entityID) { _sentFilteredEntities.insert(entityID); }
|
||||
void removeSentFilteredEntity(const QUuid& entityID) { _sentFilteredEntities.remove(entityID); }
|
||||
bool sentFilteredEntity(const QUuid& entityID) { return _sentFilteredEntities.contains(entityID); }
|
||||
bool sentFilteredEntity(const QUuid& entityID) const { return _sentFilteredEntities.contains(entityID); }
|
||||
QSet<QUuid> getSentFilteredEntities() { return _sentFilteredEntities; }
|
||||
|
||||
// the following flagged extra entity methods can only be called from the OctreeSendThread for the given Node
|
||||
|
|
|
@ -157,7 +157,7 @@ void MaterialEntityItem::setMaterialURL(const QString& materialURLString, bool u
|
|||
}
|
||||
|
||||
if (usingUserData) {
|
||||
_parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(getUserData().toUtf8()));
|
||||
_parsedMaterials = NetworkMaterialResource::parseJSONMaterials(QJsonDocument::fromJson(getUserData().toUtf8()), materialURLString);
|
||||
|
||||
// Since our material changed, the current name might not be valid anymore, so we need to update
|
||||
setCurrentMaterialName(_currentMaterialName);
|
||||
|
|
|
@ -161,23 +161,19 @@ void FBXWriter::encodeFBXProperty(QDataStream& out, const QVariant& prop) {
|
|||
case QMetaType::QString:
|
||||
{
|
||||
auto bytes = prop.toString().toUtf8();
|
||||
out << 'S';
|
||||
out << bytes.length();
|
||||
out << bytes;
|
||||
out.device()->write("S", 1);
|
||||
out << (int32_t)bytes.size();
|
||||
out.writeRawData(bytes, bytes.size());
|
||||
break;
|
||||
}
|
||||
|
||||
case QMetaType::QByteArray:
|
||||
{
|
||||
auto bytes = prop.toByteArray();
|
||||
out.device()->write("S", 1);
|
||||
out << (int32_t)bytes.size();
|
||||
out.writeRawData(bytes, bytes.size());
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
auto bytes = prop.toByteArray();
|
||||
out.device()->write("S", 1);
|
||||
out << (int32_t)bytes.size();
|
||||
out.writeRawData(bytes, bytes.size());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (prop.canConvert<QVector<float>>()) {
|
||||
|
|
|
@ -643,13 +643,13 @@ done:
|
|||
}
|
||||
|
||||
|
||||
FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url) {
|
||||
FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url) {
|
||||
PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr);
|
||||
QBuffer buffer { &model };
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
|
||||
FBXGeometry* geometryPtr = new FBXGeometry();
|
||||
FBXGeometry& geometry = *geometryPtr;
|
||||
auto geometryPtr { std::make_shared<FBXGeometry>() };
|
||||
FBXGeometry& geometry { *geometryPtr };
|
||||
OBJTokenizer tokenizer { &buffer };
|
||||
float scaleGuess = 1.0f;
|
||||
|
||||
|
|
|
@ -85,7 +85,7 @@ public:
|
|||
QString currentMaterialName;
|
||||
QHash<QString, OBJMaterial> materials;
|
||||
|
||||
FBXGeometry* readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url = QUrl());
|
||||
FBXGeometry::Pointer readOBJ(QByteArray& model, const QVariantHash& mapping, bool combineParts, const QUrl& url = QUrl());
|
||||
|
||||
private:
|
||||
QUrl _url;
|
||||
|
|
|
@ -70,6 +70,7 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) {
|
|||
}
|
||||
#endif
|
||||
|
||||
#if !defined(USE_GLES)
|
||||
if (gl::Context::enableDebugLogger()) {
|
||||
_context->makeCurrent(_offscreenSurface);
|
||||
QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(this);
|
||||
|
@ -79,6 +80,7 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) {
|
|||
logger->startLogging(QOpenGLDebugLogger::SynchronousLogging);
|
||||
_context->doneCurrent();
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
6
libraries/gpu-gl-common/CMakeLists.txt
Normal file
6
libraries/gpu-gl-common/CMakeLists.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
set(TARGET_NAME gpu-gl-common)
|
||||
setup_hifi_library(Concurrent)
|
||||
link_hifi_libraries(shared gl gpu)
|
||||
GroupSources("src")
|
||||
target_opengl()
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
//
|
||||
// GLBackend.cpp
|
||||
// libraries/gpu-gl-android/src/gpu/gl
|
||||
// libraries/gpu/src/gpu
|
||||
//
|
||||
// Created by Cristian Duarte & Gabriel Calero on 9/21/2016.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
// Created by Sam Gateau on 10/27/2014.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
@ -16,16 +16,11 @@
|
|||
#include <functional>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "../gles/GLESBackend.h"
|
||||
|
||||
#if defined(NSIGHT_FOUND)
|
||||
#include "nvToolsExt.h"
|
||||
#endif
|
||||
|
||||
#include <shared/GlobalAppProperties.h>
|
||||
#include <GPUIdent.h>
|
||||
#include <gl/QOpenGLContextWrapper.h>
|
||||
#include <QtCore/QProcessEnvironment>
|
||||
|
||||
#include "GLTexture.h"
|
||||
#include "GLShader.h"
|
||||
|
@ -33,39 +28,6 @@
|
|||
using namespace gpu;
|
||||
using namespace gpu::gl;
|
||||
|
||||
static GLBackend* INSTANCE{ nullptr };
|
||||
|
||||
BackendPointer GLBackend::createBackend() {
|
||||
// FIXME provide a mechanism to override the backend for testing
|
||||
// Where the gpuContext is initialized and where the TRUE Backend is created and assigned
|
||||
//auto version = QOpenGLContextWrapper::currentContextVersion();
|
||||
std::shared_ptr<GLBackend> result;
|
||||
|
||||
qDebug() << "Using OpenGL ES backend";
|
||||
result = std::make_shared<gpu::gles::GLESBackend>();
|
||||
|
||||
result->initInput();
|
||||
result->initTransform();
|
||||
result->initTextureManagementStage();
|
||||
|
||||
INSTANCE = result.get();
|
||||
void* voidInstance = &(*result);
|
||||
qApp->setProperty(hifi::properties::gl::BACKEND, QVariant::fromValue(voidInstance));
|
||||
return result;
|
||||
}
|
||||
|
||||
GLBackend& getBackend() {
|
||||
if (!INSTANCE) {
|
||||
INSTANCE = static_cast<GLBackend*>(qApp->property(hifi::properties::gl::BACKEND).value<void*>());
|
||||
}
|
||||
return *INSTANCE;
|
||||
}
|
||||
|
||||
bool GLBackend::makeProgram(Shader& shader, const Shader::BindingSet& slotBindings, const Shader::CompilationHandler& handler) {
|
||||
return GLShader::makeProgram(getBackend(), shader, slotBindings, handler);
|
||||
}
|
||||
|
||||
|
||||
GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] =
|
||||
{
|
||||
(&::gpu::gl::GLBackend::do_draw),
|
||||
|
@ -93,12 +55,16 @@ GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] =
|
|||
(&::gpu::gl::GLBackend::do_setUniformBuffer),
|
||||
(&::gpu::gl::GLBackend::do_setResourceBuffer),
|
||||
(&::gpu::gl::GLBackend::do_setResourceTexture),
|
||||
(&::gpu::gl::GLBackend::do_setResourceFramebufferSwapChainTexture),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_setFramebuffer),
|
||||
(&::gpu::gl::GLBackend::do_setFramebufferSwapChain),
|
||||
(&::gpu::gl::GLBackend::do_clearFramebuffer),
|
||||
(&::gpu::gl::GLBackend::do_blit),
|
||||
(&::gpu::gl::GLBackend::do_generateTextureMips),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_advance),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_beginQuery),
|
||||
(&::gpu::gl::GLBackend::do_endQuery),
|
||||
(&::gpu::gl::GLBackend::do_getQuery),
|
||||
|
@ -147,6 +113,9 @@ void GLBackend::init() {
|
|||
qCDebug(gpugllogging) << "\tcard:" << gpu->getName();
|
||||
qCDebug(gpugllogging) << "\tdriver:" << gpu->getDriver();
|
||||
qCDebug(gpugllogging) << "\tdedicated memory:" << gpu->getMemory() << "MB";
|
||||
#if !defined(USE_GLES)
|
||||
qCDebug(gpugllogging, "V-Sync is %s\n", (::gl::getSwapInterval() > 0 ? "ON" : "OFF"));
|
||||
#endif
|
||||
#if THREADED_TEXTURE_BUFFERING
|
||||
// This has to happen on the main thread in order to give the thread
|
||||
// pool a reasonable parent object
|
||||
|
@ -224,7 +193,7 @@ void GLBackend::renderPassTransfer(const Batch& batch) {
|
|||
}
|
||||
|
||||
{ // Sync the transform buffers
|
||||
PROFILE_RANGE(render_gpu_gl_detail, "transferGPUTransform");
|
||||
PROFILE_RANGE(render_gpu_gl_detail, "syncGPUTransform");
|
||||
transferTransformState(batch);
|
||||
}
|
||||
|
||||
|
@ -292,7 +261,7 @@ void GLBackend::render(const Batch& batch) {
|
|||
|
||||
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
|
||||
if (_stereo.isStereo()) {
|
||||
glEnable(GL_CLIP_DISTANCE0_EXT);
|
||||
glEnable(GL_CLIP_DISTANCE0);
|
||||
}
|
||||
#endif
|
||||
{
|
||||
|
@ -301,7 +270,7 @@ void GLBackend::render(const Batch& batch) {
|
|||
}
|
||||
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
|
||||
if (_stereo.isStereo()) {
|
||||
glDisable(GL_CLIP_DISTANCE0_EXT);
|
||||
glDisable(GL_CLIP_DISTANCE0);
|
||||
}
|
||||
#endif
|
||||
|
|
@ -32,9 +32,13 @@
|
|||
|
||||
// Different versions for the stereo drawcall
|
||||
// Current preferred is "instanced" which draw the shape twice but instanced and rely on clipping plane to draw left/right side only
|
||||
//#define GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE
|
||||
#if defined(USE_GLES)
|
||||
#define GPU_STEREO_TECHNIQUE_DOUBLED_SIMPLE
|
||||
#else
|
||||
//#define GPU_STEREO_TECHNIQUE_DOUBLED_SMARTER
|
||||
#define GPU_STEREO_TECHNIQUE_INSTANCED
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// Let these be configured by the one define picked above
|
|
@ -17,6 +17,11 @@
|
|||
using namespace gpu;
|
||||
using namespace gpu::gl;
|
||||
|
||||
#if defined(USE_GLES)
|
||||
#define GL_FRAMEBUFFER_SRGB GL_FRAMEBUFFER_SRGB_EXT
|
||||
#define glClearDepth glClearDepthf
|
||||
#endif
|
||||
|
||||
void GLBackend::syncOutputStateCache() {
|
||||
GLint currentFBO;
|
||||
glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, ¤tFBO);
|
||||
|
@ -88,7 +93,7 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) {
|
|||
if (masks & Framebuffer::BUFFER_STENCIL) {
|
||||
glClearStencil(stencil);
|
||||
glmask |= GL_STENCIL_BUFFER_BIT;
|
||||
|
||||
|
||||
cacheStencilMask = _pipeline._stateCache.stencilActivation.getWriteMaskFront();
|
||||
if (cacheStencilMask != 0xFF) {
|
||||
restoreStencilMask = true;
|
||||
|
@ -182,7 +187,11 @@ void GLBackend::downloadFramebuffer(const FramebufferPointer& srcFramebuffer, co
|
|||
return;
|
||||
}
|
||||
|
||||
#if defined(USE_GLES)
|
||||
GLenum format = GL_RGBA;
|
||||
#else
|
||||
GLenum format = GL_BGRA;
|
||||
#endif
|
||||
if (destImage.format() != QImage::Format_ARGB32) {
|
||||
qCWarning(gpugllogging) << "GLBackend::downloadFramebuffer : destImage format must be FORMAT_ARGB32 to receive the region of the framebuffer";
|
||||
return;
|
|
@ -25,6 +25,7 @@ static bool timeElapsed = false;
|
|||
#endif
|
||||
|
||||
void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) {
|
||||
#if !defined(USE_GLES)
|
||||
auto query = batch._queries.get(batch._params[paramOffset]._uint);
|
||||
GLQuery* glquery = syncGPUObject(*query);
|
||||
if (glquery) {
|
||||
|
@ -43,9 +44,11 @@ void GLBackend::do_beginQuery(const Batch& batch, size_t paramOffset) {
|
|||
glquery->_rangeQueryDepth = _queryStage._rangeQueryDepth;
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void GLBackend::do_endQuery(const Batch& batch, size_t paramOffset) {
|
||||
#if !defined(USE_GLES)
|
||||
auto query = batch._queries.get(batch._params[paramOffset]._uint);
|
||||
GLQuery* glquery = syncGPUObject(*query);
|
||||
if (glquery) {
|
||||
|
@ -66,9 +69,11 @@ void GLBackend::do_endQuery(const Batch& batch, size_t paramOffset) {
|
|||
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) {
|
||||
#if !defined(USE_GLES)
|
||||
auto query = batch._queries.get(batch._params[paramOffset]._uint);
|
||||
GLQuery* glquery = syncGPUObject(*query);
|
||||
if (glquery) {
|
||||
|
@ -90,6 +95,7 @@ void GLBackend::do_getQuery(const Batch& batch, size_t paramOffset) {
|
|||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void GLBackend::resetQueryStage() {
|
|
@ -14,15 +14,34 @@ using namespace gpu::gl;
|
|||
|
||||
// GLSL version
|
||||
std::string GLBackend::getBackendShaderHeader() const {
|
||||
return std::string("#version 410 core");
|
||||
|
||||
#if defined(USE_GLES)
|
||||
static const std::string header(
|
||||
R"SHADER(
|
||||
#version 310 es
|
||||
#extension GL_EXT_texture_buffer : enable
|
||||
precision lowp float; // check precision 2
|
||||
precision lowp samplerBuffer;
|
||||
precision lowp sampler2DShadow;
|
||||
)SHADER");
|
||||
#else
|
||||
static const std::string header(
|
||||
R"SHADER(
|
||||
#version 410 core
|
||||
)SHADER");
|
||||
#endif
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
|
||||
// Shader domain
|
||||
static const size_t NUM_SHADER_DOMAINS = 3;
|
||||
static_assert(Shader::Type::NUM_DOMAINS == NUM_SHADER_DOMAINS, "GL shader domains must equal defined GPU shader domains");
|
||||
|
||||
// GL Shader type enums
|
||||
// Must match the order of type specified in gpu::Shader::Type
|
||||
static const std::array<GLenum, NUM_SHADER_DOMAINS> SHADER_DOMAINS { {
|
||||
static const std::array<GLenum, NUM_SHADER_DOMAINS> SHADER_DOMAINS{ {
|
||||
GL_VERTEX_SHADER,
|
||||
GL_FRAGMENT_SHADER,
|
||||
GL_GEOMETRY_SHADER,
|
||||
|
@ -30,22 +49,33 @@ static const std::array<GLenum, NUM_SHADER_DOMAINS> SHADER_DOMAINS { {
|
|||
|
||||
// Domain specific defines
|
||||
// Must match the order of type specified in gpu::Shader::Type
|
||||
static const std::array<std::string, NUM_SHADER_DOMAINS> DOMAIN_DEFINES { {
|
||||
static const std::array<std::string, NUM_SHADER_DOMAINS> DOMAIN_DEFINES{ {
|
||||
"#define GPU_VERTEX_SHADER",
|
||||
"#define GPU_PIXEL_SHADER",
|
||||
"#define GPU_GEOMETRY_SHADER",
|
||||
} };
|
||||
|
||||
// Stereo specific defines
|
||||
static const std::string stereoVersion {
|
||||
static const std::string stereoVersion{
|
||||
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
|
||||
"#define GPU_TRANSFORM_IS_STEREO\n#define GPU_TRANSFORM_STEREO_CAMERA\n#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED\n#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN"
|
||||
R"SHADER(
|
||||
#define GPU_TRANSFORM_IS_STEREO
|
||||
#define GPU_TRANSFORM_STEREO_CAMERA
|
||||
#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED
|
||||
#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN
|
||||
)SHADER"
|
||||
#endif
|
||||
#ifdef GPU_STEREO_DRAWCALL_DOUBLED
|
||||
#ifdef GPU_STEREO_CAMERA_BUFFER
|
||||
"#define GPU_TRANSFORM_IS_STEREO\n#define GPU_TRANSFORM_STEREO_CAMERA\n#define GPU_TRANSFORM_STEREO_CAMERA_ATTRIBUTED"
|
||||
R"SHADER(
|
||||
#define GPU_TRANSFORM_IS_STEREO
|
||||
#define GPU_TRANSFORM_STEREO_CAMERA
|
||||
#define GPU_TRANSFORM_STEREO_CAMERA_ATTRIBUTED
|
||||
)SHADER"
|
||||
#else
|
||||
"#define GPU_TRANSFORM_IS_STEREO"
|
||||
R"SHADER(
|
||||
#define GPU_TRANSFORM_IS_STEREO
|
||||
)SHADER"
|
||||
#endif
|
||||
#endif
|
||||
};
|
||||
|
@ -67,7 +97,10 @@ GLShader* GLBackend::compileBackendShader(const Shader& shader, const Shader::Co
|
|||
for (int version = 0; version < GLShader::NumVersions; version++) {
|
||||
auto& shaderObject = shaderObjects[version];
|
||||
|
||||
std::string shaderDefines = getBackendShaderHeader() + "\n" + DOMAIN_DEFINES[shader.getType()] + "\n" + VERSION_DEFINES[version];
|
||||
std::string shaderDefines = getBackendShaderHeader() + "\n"
|
||||
+ DOMAIN_DEFINES[shader.getType()] + "\n"
|
||||
+ VERSION_DEFINES[version];
|
||||
|
||||
if (handler) {
|
||||
bool retest = true;
|
||||
std::string currentSrc = shaderSource;
|
||||
|
@ -154,149 +187,173 @@ GLShader* GLBackend::compileBackendProgram(const Shader& program, const Shader::
|
|||
return object;
|
||||
}
|
||||
|
||||
|
||||
GLBackend::ElementResource GLBackend::getFormatFromGLUniform(GLenum gltype) {
|
||||
switch (gltype) {
|
||||
case GL_FLOAT: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
case GL_FLOAT_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
case GL_FLOAT_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
case GL_FLOAT_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
/*
|
||||
case GL_DOUBLE: return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
case GL_DOUBLE_VEC2: return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
case GL_DOUBLE_VEC3: return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
case GL_DOUBLE_VEC4: return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
*/
|
||||
case GL_INT: return ElementResource(Element(SCALAR, gpu::INT32, UNIFORM), Resource::BUFFER);
|
||||
case GL_INT_VEC2: return ElementResource(Element(VEC2, gpu::INT32, UNIFORM), Resource::BUFFER);
|
||||
case GL_INT_VEC3: return ElementResource(Element(VEC3, gpu::INT32, UNIFORM), Resource::BUFFER);
|
||||
case GL_INT_VEC4: return ElementResource(Element(VEC4, gpu::INT32, UNIFORM), Resource::BUFFER);
|
||||
case GL_FLOAT:
|
||||
return ElementResource(Element(SCALAR, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
case GL_FLOAT_VEC2:
|
||||
return ElementResource(Element(VEC2, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
case GL_FLOAT_VEC3:
|
||||
return ElementResource(Element(VEC3, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
case GL_FLOAT_VEC4:
|
||||
return ElementResource(Element(VEC4, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
|
||||
case GL_UNSIGNED_INT: return ElementResource(Element(SCALAR, gpu::UINT32, UNIFORM), Resource::BUFFER);
|
||||
#if defined(Q_OS_WIN)
|
||||
case GL_UNSIGNED_INT_VEC2: return ElementResource(Element(VEC2, gpu::UINT32, UNIFORM), Resource::BUFFER);
|
||||
case GL_UNSIGNED_INT_VEC3: return ElementResource(Element(VEC3, gpu::UINT32, UNIFORM), Resource::BUFFER);
|
||||
case GL_UNSIGNED_INT_VEC4: return ElementResource(Element(VEC4, gpu::UINT32, UNIFORM), Resource::BUFFER);
|
||||
case GL_INT:
|
||||
return ElementResource(Element(SCALAR, gpu::INT32, UNIFORM), Resource::BUFFER);
|
||||
case GL_INT_VEC2:
|
||||
return ElementResource(Element(VEC2, gpu::INT32, UNIFORM), Resource::BUFFER);
|
||||
case GL_INT_VEC3:
|
||||
return ElementResource(Element(VEC3, gpu::INT32, UNIFORM), Resource::BUFFER);
|
||||
case GL_INT_VEC4:
|
||||
return ElementResource(Element(VEC4, gpu::INT32, UNIFORM), Resource::BUFFER);
|
||||
|
||||
case GL_UNSIGNED_INT:
|
||||
return ElementResource(Element(SCALAR, gpu::UINT32, UNIFORM), Resource::BUFFER);
|
||||
case GL_UNSIGNED_INT_VEC2:
|
||||
return ElementResource(Element(VEC2, gpu::UINT32, UNIFORM), Resource::BUFFER);
|
||||
case GL_UNSIGNED_INT_VEC3:
|
||||
return ElementResource(Element(VEC3, gpu::UINT32, UNIFORM), Resource::BUFFER);
|
||||
case GL_UNSIGNED_INT_VEC4:
|
||||
return ElementResource(Element(VEC4, gpu::UINT32, UNIFORM), Resource::BUFFER);
|
||||
|
||||
case GL_BOOL:
|
||||
return ElementResource(Element(SCALAR, gpu::BOOL, UNIFORM), Resource::BUFFER);
|
||||
case GL_BOOL_VEC2:
|
||||
return ElementResource(Element(VEC2, gpu::BOOL, UNIFORM), Resource::BUFFER);
|
||||
case GL_BOOL_VEC3:
|
||||
return ElementResource(Element(VEC3, gpu::BOOL, UNIFORM), Resource::BUFFER);
|
||||
case GL_BOOL_VEC4:
|
||||
return ElementResource(Element(VEC4, gpu::BOOL, UNIFORM), Resource::BUFFER);
|
||||
|
||||
case GL_FLOAT_MAT2:
|
||||
return ElementResource(Element(gpu::MAT2, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
case GL_FLOAT_MAT3:
|
||||
return ElementResource(Element(MAT3, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
case GL_FLOAT_MAT4:
|
||||
return ElementResource(Element(MAT4, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
|
||||
case GL_SAMPLER_2D:
|
||||
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D);
|
||||
case GL_SAMPLER_3D:
|
||||
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_3D);
|
||||
case GL_SAMPLER_CUBE:
|
||||
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_CUBE);
|
||||
case GL_SAMPLER_2D_MULTISAMPLE:
|
||||
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D);
|
||||
case GL_SAMPLER_2D_ARRAY:
|
||||
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D_ARRAY);
|
||||
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
|
||||
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY);
|
||||
case GL_SAMPLER_2D_SHADOW:
|
||||
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D);
|
||||
case GL_SAMPLER_CUBE_SHADOW:
|
||||
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_CUBE);
|
||||
case GL_SAMPLER_2D_ARRAY_SHADOW:
|
||||
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D_ARRAY);
|
||||
case GL_SAMPLER_BUFFER:
|
||||
return ElementResource(Element(SCALAR, gpu::FLOAT, RESOURCE_BUFFER), Resource::BUFFER);
|
||||
case GL_INT_SAMPLER_2D:
|
||||
return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D);
|
||||
case GL_INT_SAMPLER_2D_MULTISAMPLE:
|
||||
return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D);
|
||||
case GL_INT_SAMPLER_3D:
|
||||
return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_3D);
|
||||
case GL_INT_SAMPLER_CUBE:
|
||||
return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_CUBE);
|
||||
case GL_INT_SAMPLER_2D_ARRAY:
|
||||
return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D_ARRAY);
|
||||
case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
|
||||
return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY);
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D:
|
||||
return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D);
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE:
|
||||
return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D);
|
||||
case GL_UNSIGNED_INT_SAMPLER_3D:
|
||||
return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_3D);
|
||||
case GL_UNSIGNED_INT_SAMPLER_CUBE:
|
||||
return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_CUBE);
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
|
||||
return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D_ARRAY);
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
|
||||
return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY);
|
||||
|
||||
#if !defined(USE_GLES)
|
||||
case GL_SAMPLER_1D:
|
||||
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D);
|
||||
case GL_SAMPLER_1D_ARRAY:
|
||||
return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D_ARRAY);
|
||||
case GL_INT_SAMPLER_1D:
|
||||
return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D);
|
||||
case GL_INT_SAMPLER_1D_ARRAY:
|
||||
return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D_ARRAY);
|
||||
case GL_UNSIGNED_INT_SAMPLER_1D:
|
||||
return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D);
|
||||
case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY:
|
||||
return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D_ARRAY);
|
||||
#endif
|
||||
|
||||
case GL_BOOL: return ElementResource(Element(SCALAR, gpu::BOOL, UNIFORM), Resource::BUFFER);
|
||||
case GL_BOOL_VEC2: return ElementResource(Element(VEC2, gpu::BOOL, UNIFORM), Resource::BUFFER);
|
||||
case GL_BOOL_VEC3: return ElementResource(Element(VEC3, gpu::BOOL, UNIFORM), Resource::BUFFER);
|
||||
case GL_BOOL_VEC4: return ElementResource(Element(VEC4, gpu::BOOL, UNIFORM), Resource::BUFFER);
|
||||
|
||||
|
||||
case GL_FLOAT_MAT2: return ElementResource(Element(gpu::MAT2, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
case GL_FLOAT_MAT3: return ElementResource(Element(MAT3, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
case GL_FLOAT_MAT4: return ElementResource(Element(MAT4, gpu::FLOAT, UNIFORM), Resource::BUFFER);
|
||||
|
||||
/* {GL_FLOAT_MAT2x3 mat2x3},
|
||||
{GL_FLOAT_MAT2x4 mat2x4},
|
||||
{GL_FLOAT_MAT3x2 mat3x2},
|
||||
{GL_FLOAT_MAT3x4 mat3x4},
|
||||
{GL_FLOAT_MAT4x2 mat4x2},
|
||||
{GL_FLOAT_MAT4x3 mat4x3},
|
||||
{GL_DOUBLE_MAT2 dmat2},
|
||||
{GL_DOUBLE_MAT3 dmat3},
|
||||
{GL_DOUBLE_MAT4 dmat4},
|
||||
{GL_DOUBLE_MAT2x3 dmat2x3},
|
||||
{GL_DOUBLE_MAT2x4 dmat2x4},
|
||||
{GL_DOUBLE_MAT3x2 dmat3x2},
|
||||
{GL_DOUBLE_MAT3x4 dmat3x4},
|
||||
{GL_DOUBLE_MAT4x2 dmat4x2},
|
||||
{GL_DOUBLE_MAT4x3 dmat4x3},
|
||||
*/
|
||||
|
||||
case GL_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D);
|
||||
case GL_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D);
|
||||
|
||||
case GL_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_3D);
|
||||
case GL_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_CUBE);
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
case GL_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D);
|
||||
case GL_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_1D_ARRAY);
|
||||
case GL_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER), Resource::TEXTURE_2D_ARRAY);
|
||||
case GL_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY);
|
||||
#endif
|
||||
|
||||
case GL_SAMPLER_2D_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D);
|
||||
#if defined(Q_OS_WIN)
|
||||
case GL_SAMPLER_CUBE_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_CUBE);
|
||||
|
||||
case GL_SAMPLER_2D_ARRAY_SHADOW: return ElementResource(Element(SCALAR, gpu::FLOAT, SAMPLER_SHADOW), Resource::TEXTURE_2D_ARRAY);
|
||||
#endif
|
||||
|
||||
// {GL_SAMPLER_1D_SHADOW sampler1DShadow},
|
||||
// {GL_SAMPLER_1D_ARRAY_SHADOW sampler1DArrayShadow},
|
||||
|
||||
case GL_SAMPLER_BUFFER: return ElementResource(Element(SCALAR, gpu::FLOAT, RESOURCE_BUFFER), Resource::BUFFER);
|
||||
|
||||
// {GL_SAMPLER_2D_RECT sampler2DRect},
|
||||
// {GL_SAMPLER_2D_RECT_SHADOW sampler2DRectShadow},
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
case GL_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D);
|
||||
case GL_INT_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D);
|
||||
case GL_INT_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D);
|
||||
case GL_INT_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_3D);
|
||||
case GL_INT_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_CUBE);
|
||||
|
||||
case GL_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_1D_ARRAY);
|
||||
case GL_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER), Resource::TEXTURE_2D_ARRAY);
|
||||
case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::INT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY);
|
||||
|
||||
// {GL_INT_SAMPLER_BUFFER isamplerBuffer},
|
||||
// {GL_INT_SAMPLER_2D_RECT isampler2DRect},
|
||||
|
||||
case GL_UNSIGNED_INT_SAMPLER_1D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D);
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D);
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D);
|
||||
case GL_UNSIGNED_INT_SAMPLER_3D: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_3D);
|
||||
case GL_UNSIGNED_INT_SAMPLER_CUBE: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_CUBE);
|
||||
|
||||
case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_1D_ARRAY);
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER), Resource::TEXTURE_2D_ARRAY);
|
||||
case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY: return ElementResource(Element(SCALAR, gpu::UINT32, SAMPLER_MULTISAMPLE), Resource::TEXTURE_2D_ARRAY);
|
||||
#endif
|
||||
// {GL_UNSIGNED_INT_SAMPLER_BUFFER usamplerBuffer},
|
||||
// {GL_UNSIGNED_INT_SAMPLER_2D_RECT usampler2DRect},
|
||||
/*
|
||||
{GL_IMAGE_1D image1D},
|
||||
{GL_IMAGE_2D image2D},
|
||||
{GL_IMAGE_3D image3D},
|
||||
{GL_IMAGE_2D_RECT image2DRect},
|
||||
{GL_IMAGE_CUBE imageCube},
|
||||
{GL_IMAGE_BUFFER imageBuffer},
|
||||
{GL_IMAGE_1D_ARRAY image1DArray},
|
||||
{GL_IMAGE_2D_ARRAY image2DArray},
|
||||
{GL_IMAGE_2D_MULTISAMPLE image2DMS},
|
||||
{GL_IMAGE_2D_MULTISAMPLE_ARRAY image2DMSArray},
|
||||
{GL_INT_IMAGE_1D iimage1D},
|
||||
{GL_INT_IMAGE_2D iimage2D},
|
||||
{GL_INT_IMAGE_3D iimage3D},
|
||||
{GL_INT_IMAGE_2D_RECT iimage2DRect},
|
||||
{GL_INT_IMAGE_CUBE iimageCube},
|
||||
{GL_INT_IMAGE_BUFFER iimageBuffer},
|
||||
{GL_INT_IMAGE_1D_ARRAY iimage1DArray},
|
||||
{GL_INT_IMAGE_2D_ARRAY iimage2DArray},
|
||||
{GL_INT_IMAGE_2D_MULTISAMPLE iimage2DMS},
|
||||
{GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY iimage2DMSArray},
|
||||
{GL_UNSIGNED_INT_IMAGE_1D uimage1D},
|
||||
{GL_UNSIGNED_INT_IMAGE_2D uimage2D},
|
||||
{GL_UNSIGNED_INT_IMAGE_3D uimage3D},
|
||||
{GL_UNSIGNED_INT_IMAGE_2D_RECT uimage2DRect},
|
||||
{GL_UNSIGNED_INT_IMAGE_CUBE uimageCube},+ [0] {_name="fInnerRadius" _location=0 _element={_semantic=15 '\xf' _dimension=0 '\0' _type=0 '\0' } } gpu::Shader::Slot
|
||||
|
||||
{GL_UNSIGNED_INT_IMAGE_BUFFER uimageBuffer},
|
||||
{GL_UNSIGNED_INT_IMAGE_1D_ARRAY uimage1DArray},
|
||||
{GL_UNSIGNED_INT_IMAGE_2D_ARRAY uimage2DArray},
|
||||
{GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE uimage2DMS},
|
||||
{GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY uimage2DMSArray},
|
||||
{GL_UNSIGNED_INT_ATOMIC_COUNTER atomic_uint}
|
||||
*/
|
||||
default:
|
||||
return ElementResource(Element(), Resource::BUFFER);
|
||||
}
|
||||
|
||||
// Non-covered types
|
||||
//{GL_FLOAT_MAT2x3 mat2x3},
|
||||
//{GL_FLOAT_MAT2x4 mat2x4},
|
||||
//{GL_FLOAT_MAT3x2 mat3x2},
|
||||
//{GL_FLOAT_MAT3x4 mat3x4},
|
||||
//{GL_FLOAT_MAT4x2 mat4x2},
|
||||
//{GL_FLOAT_MAT4x3 mat4x3},
|
||||
//{GL_DOUBLE_MAT2 dmat2},
|
||||
//{GL_DOUBLE_MAT3 dmat3},
|
||||
//{GL_DOUBLE_MAT4 dmat4},
|
||||
//{GL_DOUBLE_MAT2x3 dmat2x3},
|
||||
//{GL_DOUBLE_MAT2x4 dmat2x4},
|
||||
//{GL_DOUBLE_MAT3x2 dmat3x2},
|
||||
//{GL_DOUBLE_MAT3x4 dmat3x4},
|
||||
//{GL_DOUBLE_MAT4x2 dmat4x2},
|
||||
//{GL_DOUBLE_MAT4x3 dmat4x3},
|
||||
//{GL_SAMPLER_1D_SHADOW sampler1DShadow},
|
||||
//{GL_SAMPLER_1D_ARRAY_SHADOW sampler1DArrayShadow},
|
||||
//{GL_SAMPLER_2D_RECT sampler2DRect},
|
||||
//{GL_SAMPLER_2D_RECT_SHADOW sampler2DRectShadow},
|
||||
//{GL_INT_SAMPLER_BUFFER isamplerBuffer},
|
||||
//{GL_INT_SAMPLER_2D_RECT isampler2DRect},
|
||||
//{GL_UNSIGNED_INT_SAMPLER_BUFFER usamplerBuffer},
|
||||
//{GL_UNSIGNED_INT_SAMPLER_2D_RECT usampler2DRect},
|
||||
//{GL_IMAGE_1D image1D},
|
||||
//{GL_IMAGE_2D image2D},
|
||||
//{GL_IMAGE_3D image3D},
|
||||
//{GL_IMAGE_2D_RECT image2DRect},
|
||||
//{GL_IMAGE_CUBE imageCube},
|
||||
//{GL_IMAGE_BUFFER imageBuffer},
|
||||
//{GL_IMAGE_1D_ARRAY image1DArray},
|
||||
//{GL_IMAGE_2D_ARRAY image2DArray},
|
||||
//{GL_IMAGE_2D_MULTISAMPLE image2DMS},
|
||||
//{GL_IMAGE_2D_MULTISAMPLE_ARRAY image2DMSArray},
|
||||
//{GL_INT_IMAGE_1D iimage1D},
|
||||
//{GL_INT_IMAGE_2D iimage2D},
|
||||
//{GL_INT_IMAGE_3D iimage3D},
|
||||
//{GL_INT_IMAGE_2D_RECT iimage2DRect},
|
||||
//{GL_INT_IMAGE_CUBE iimageCube},
|
||||
//{GL_INT_IMAGE_BUFFER iimageBuffer},
|
||||
//{GL_INT_IMAGE_1D_ARRAY iimage1DArray},
|
||||
//{GL_INT_IMAGE_2D_ARRAY iimage2DArray},
|
||||
//{GL_INT_IMAGE_2D_MULTISAMPLE iimage2DMS},
|
||||
//{GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY iimage2DMSArray},
|
||||
//{GL_UNSIGNED_INT_IMAGE_1D uimage1D},
|
||||
//{GL_UNSIGNED_INT_IMAGE_2D uimage2D},
|
||||
//{GL_UNSIGNED_INT_IMAGE_3D uimage3D},
|
||||
//{GL_UNSIGNED_INT_IMAGE_2D_RECT uimage2DRect},
|
||||
//{GL_UNSIGNED_INT_IMAGE_CUBE uimageCube},
|
||||
//{GL_UNSIGNED_INT_IMAGE_BUFFER uimageBuffer},
|
||||
//{GL_UNSIGNED_INT_IMAGE_1D_ARRAY uimage1DArray},
|
||||
//{GL_UNSIGNED_INT_IMAGE_2D_ARRAY uimage2DArray},
|
||||
//{GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE uimage2DMS},
|
||||
//{GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY uimage2DMSArray},
|
||||
//{GL_UNSIGNED_INT_ATOMIC_COUNTER atomic_uint}
|
||||
|
||||
|
||||
};
|
||||
|
||||
int GLBackend::makeUniformSlots(GLuint glprogram, const Shader::BindingSet& slotBindings,
|
||||
|
@ -550,4 +607,3 @@ void GLBackend::makeProgramBindings(ShaderObject& shaderObject) {
|
|||
qCWarning(gpugllogging) << "GLShader::makeBindings - failed to link after assigning slotBindings?";
|
||||
}
|
||||
}
|
||||
|
|
@ -29,18 +29,19 @@ void GLBackend::resetPipelineState(State::Signature nextSignature) {
|
|||
}
|
||||
}
|
||||
|
||||
// Default line width accross the board
|
||||
glLineWidth(1.0f);
|
||||
#if !defined(USE_GLES)
|
||||
// force a few states regardless
|
||||
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
|
||||
|
||||
// Point size is always on
|
||||
// FIXME CORE
|
||||
//glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
|
||||
glEnable(GL_PROGRAM_POINT_SIZE_EXT);
|
||||
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
|
||||
|
||||
// Default line width accross the board
|
||||
glLineWidth(1.0f);
|
||||
glEnable(GL_LINE_SMOOTH);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
@ -48,17 +49,19 @@ void GLBackend::syncPipelineStateCache() {
|
|||
State::Data state;
|
||||
|
||||
// force a few states regardless
|
||||
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
|
||||
|
||||
// Point size is always on
|
||||
// FIXME CORE
|
||||
//glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
|
||||
glEnable(GL_PROGRAM_POINT_SIZE_EXT);
|
||||
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
|
||||
|
||||
// Default line width accross the board
|
||||
glLineWidth(1.0f);
|
||||
|
||||
#if !defined(USE_GLES)
|
||||
glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS);
|
||||
|
||||
// Point size is always on
|
||||
//glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
|
||||
glEnable(GL_PROGRAM_POINT_SIZE_EXT);
|
||||
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
|
||||
glEnable(GL_LINE_SMOOTH);
|
||||
#endif
|
||||
|
||||
getCurrentGLState(state);
|
||||
State::Signature signature = State::evalSignature(state);
|
||||
|
@ -70,11 +73,13 @@ void GLBackend::syncPipelineStateCache() {
|
|||
|
||||
void GLBackend::do_setStateFillMode(int32 mode) {
|
||||
if (_pipeline._stateCache.fillMode != mode) {
|
||||
#if !defined(USE_GLES)
|
||||
static GLenum GL_FILL_MODES[] = { GL_POINT, GL_LINE, GL_FILL };
|
||||
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL_MODES[mode]);
|
||||
(void)CHECK_GL_ERROR();
|
||||
|
||||
_pipeline._stateCache.fillMode = State::FillMode(mode);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,14 +111,15 @@ void GLBackend::do_setStateFrontFaceClockwise(bool isClockwise) {
|
|||
|
||||
void GLBackend::do_setStateDepthClampEnable(bool enable) {
|
||||
if (_pipeline._stateCache.depthClampEnable != enable) {
|
||||
#if !defined(USE_GLES)
|
||||
if (enable) {
|
||||
glEnable(GL_DEPTH_CLAMP);
|
||||
} else {
|
||||
glDisable(GL_DEPTH_CLAMP);
|
||||
}
|
||||
(void)CHECK_GL_ERROR();
|
||||
|
||||
_pipeline._stateCache.depthClampEnable = enable;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,6 +138,7 @@ void GLBackend::do_setStateScissorEnable(bool enable) {
|
|||
|
||||
void GLBackend::do_setStateMultisampleEnable(bool enable) {
|
||||
if (_pipeline._stateCache.multisampleEnable != enable) {
|
||||
#if !defined(USE_GLES)
|
||||
if (enable) {
|
||||
glEnable(GL_MULTISAMPLE);
|
||||
} else {
|
||||
|
@ -140,11 +147,13 @@ void GLBackend::do_setStateMultisampleEnable(bool enable) {
|
|||
(void)CHECK_GL_ERROR();
|
||||
|
||||
_pipeline._stateCache.multisampleEnable = enable;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void GLBackend::do_setStateAntialiasedLineEnable(bool enable) {
|
||||
if (_pipeline._stateCache.antialisedLineEnable != enable) {
|
||||
#if !defined(USE_GLES)
|
||||
if (enable) {
|
||||
glEnable(GL_LINE_SMOOTH);
|
||||
} else {
|
||||
|
@ -153,6 +162,7 @@ void GLBackend::do_setStateAntialiasedLineEnable(bool enable) {
|
|||
(void)CHECK_GL_ERROR();
|
||||
|
||||
_pipeline._stateCache.antialisedLineEnable = enable;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,13 +170,17 @@ void GLBackend::do_setStateDepthBias(Vec2 bias) {
|
|||
if ((bias.x != _pipeline._stateCache.depthBias) || (bias.y != _pipeline._stateCache.depthBiasSlopeScale)) {
|
||||
if ((bias.x != 0.0f) || (bias.y != 0.0f)) {
|
||||
glEnable(GL_POLYGON_OFFSET_FILL);
|
||||
#if !defined(USE_GLES)
|
||||
glEnable(GL_POLYGON_OFFSET_LINE);
|
||||
glEnable(GL_POLYGON_OFFSET_POINT);
|
||||
#endif
|
||||
glPolygonOffset(bias.x, bias.y);
|
||||
} else {
|
||||
glDisable(GL_POLYGON_OFFSET_FILL);
|
||||
#if !defined(USE_GLES)
|
||||
glDisable(GL_POLYGON_OFFSET_LINE);
|
||||
glDisable(GL_POLYGON_OFFSET_POINT);
|
||||
#endif
|
||||
}
|
||||
(void)CHECK_GL_ERROR();
|
||||
|
|
@ -33,14 +33,18 @@ bool GLFramebuffer::checkStatus() const {
|
|||
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
|
||||
qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT.";
|
||||
break;
|
||||
case GL_FRAMEBUFFER_UNSUPPORTED:
|
||||
qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED.";
|
||||
break;
|
||||
#if !defined(USE_GLES)
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
|
||||
qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER.";
|
||||
break;
|
||||
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
|
||||
qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER.";
|
||||
break;
|
||||
case GL_FRAMEBUFFER_UNSUPPORTED:
|
||||
qCWarning(gpugllogging) << "GLFramebuffer::syncGPUObject : Framebuffer not valid, GL_FRAMEBUFFER_UNSUPPORTED.";
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
|
@ -24,6 +24,7 @@ namespace gpu { namespace gl {
|
|||
|
||||
gpu::Size getFreeDedicatedMemory() {
|
||||
Size result { 0 };
|
||||
#if !defined(USE_GLES)
|
||||
static bool nvidiaMemorySupported { true };
|
||||
static bool atiMemorySupported { true };
|
||||
if (nvidiaMemorySupported) {
|
||||
|
@ -45,6 +46,7 @@ gpu::Size getFreeDedicatedMemory() {
|
|||
atiMemorySupported = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -144,6 +146,9 @@ State::BlendArg blendArgFromGL(GLenum blendArg) {
|
|||
|
||||
void getCurrentGLState(State::Data& state) {
|
||||
{
|
||||
#if defined(USE_GLES)
|
||||
state.fillMode = State::FILL_FACE;
|
||||
#else
|
||||
GLint modes[2];
|
||||
glGetIntegerv(GL_POLYGON_MODE, modes);
|
||||
if (modes[0] == GL_FILL) {
|
||||
|
@ -155,6 +160,7 @@ void getCurrentGLState(State::Data& state) {
|
|||
state.fillMode = State::FILL_POINT;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
{
|
||||
if (glIsEnabled(GL_CULL_FACE)) {
|
||||
|
@ -169,10 +175,16 @@ void getCurrentGLState(State::Data& state) {
|
|||
GLint winding;
|
||||
glGetIntegerv(GL_FRONT_FACE, &winding);
|
||||
state.frontFaceClockwise = (winding == GL_CW);
|
||||
state.depthClampEnable = glIsEnabled(GL_DEPTH_CLAMP);
|
||||
state.scissorEnable = glIsEnabled(GL_SCISSOR_TEST);
|
||||
#if defined(USE_GLES)
|
||||
state.multisampleEnable = glIsEnabled(GL_MULTISAMPLE_EXT);
|
||||
state.antialisedLineEnable = false;
|
||||
state.depthClampEnable = false;
|
||||
#else
|
||||
state.multisampleEnable = glIsEnabled(GL_MULTISAMPLE);
|
||||
state.antialisedLineEnable = glIsEnabled(GL_LINE_SMOOTH);
|
||||
state.depthClampEnable = glIsEnabled(GL_DEPTH_CLAMP);
|
||||
#endif
|
||||
state.scissorEnable = glIsEnabled(GL_SCISSOR_TEST);
|
||||
}
|
||||
{
|
||||
if (glIsEnabled(GL_POLYGON_OFFSET_FILL)) {
|
||||
|
@ -269,6 +281,7 @@ void getCurrentGLState(State::Data& state) {
|
|||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
|
||||
void serverWait() {
|
||||
auto fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
assert(fence);
|
|
@ -11,15 +11,81 @@
|
|||
using namespace gpu;
|
||||
using namespace gpu::gl;
|
||||
|
||||
#if defined(USE_GLES)
|
||||
// Missing GL formats
|
||||
#define GL_R16 GL_R16_EXT
|
||||
#define GL_R16_SNORM GL_R16_SNORM_EXT
|
||||
#define GL_RG16 GL_RG16_EXT
|
||||
#define GL_RG16_SNORM GL_RG16_SNORM_EXT
|
||||
#define GL_RGBA2 GL_RGBA8
|
||||
#define GL_RGBA16 GL_RGBA16_EXT
|
||||
#define GL_RGBA16_SNORM GL_RGBA16_SNORM_EXT
|
||||
#define GL_DEPTH_COMPONENT32 GL_DEPTH_COMPONENT32_OES
|
||||
#define GL_SLUMINANCE8_EXT GL_SLUMINANCE8_NV
|
||||
// Missing GL compressed formats
|
||||
#define GL_COMPRESSED_RED_RGTC1 0x8DBB
|
||||
#define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC
|
||||
#define GL_COMPRESSED_RG_RGTC2 0x8DBD
|
||||
#define GL_COMPRESSED_SIGNED_RG_RGTC2 0x8DBE
|
||||
#define GL_COMPRESSED_RGBA_BPTC_UNORM 0x8E8C
|
||||
#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM 0x8E8D
|
||||
#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT 0x8E8E
|
||||
#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT 0x8E8F
|
||||
#endif
|
||||
|
||||
bool GLTexelFormat::isCompressed() const {
|
||||
switch (internalFormat) {
|
||||
case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
|
||||
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
|
||||
case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
|
||||
|
||||
case GL_COMPRESSED_R11_EAC:
|
||||
case GL_COMPRESSED_SIGNED_R11_EAC:
|
||||
case GL_COMPRESSED_RG11_EAC:
|
||||
case GL_COMPRESSED_SIGNED_RG11_EAC:
|
||||
case GL_COMPRESSED_RGB8_ETC2:
|
||||
case GL_COMPRESSED_SRGB8_ETC2:
|
||||
case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
|
||||
case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
|
||||
case GL_COMPRESSED_RGBA8_ETC2_EAC:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
|
||||
case GL_COMPRESSED_RED_RGTC1:
|
||||
case GL_COMPRESSED_RG_RGTC2:
|
||||
case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
|
||||
case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
|
||||
|
||||
#if defined(USE_GLES)
|
||||
case GL_COMPRESSED_RGBA_ASTC_4x4:
|
||||
case GL_COMPRESSED_RGBA_ASTC_5x4:
|
||||
case GL_COMPRESSED_RGBA_ASTC_5x5:
|
||||
case GL_COMPRESSED_RGBA_ASTC_6x5:
|
||||
case GL_COMPRESSED_RGBA_ASTC_6x6:
|
||||
case GL_COMPRESSED_RGBA_ASTC_8x5:
|
||||
case GL_COMPRESSED_RGBA_ASTC_8x6:
|
||||
case GL_COMPRESSED_RGBA_ASTC_8x8:
|
||||
case GL_COMPRESSED_RGBA_ASTC_10x5:
|
||||
case GL_COMPRESSED_RGBA_ASTC_10x6:
|
||||
case GL_COMPRESSED_RGBA_ASTC_10x8:
|
||||
case GL_COMPRESSED_RGBA_ASTC_10x10:
|
||||
case GL_COMPRESSED_RGBA_ASTC_12x10:
|
||||
case GL_COMPRESSED_RGBA_ASTC_12x12:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10:
|
||||
case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12:
|
||||
#endif
|
||||
|
||||
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
@ -390,7 +456,11 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
|
|||
switch (srcFormat.getSemantic()) {
|
||||
case gpu::BGRA:
|
||||
case gpu::SBGRA:
|
||||
#if defined(USE_GLES)
|
||||
texel.format = GL_RGBA;
|
||||
#else
|
||||
texel.format = GL_BGRA;
|
||||
#endif
|
||||
break;
|
||||
case gpu::RGB:
|
||||
case gpu::RGBA:
|
||||
|
@ -427,8 +497,10 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
|
|||
switch (srcFormat.getSemantic()) {
|
||||
case gpu::BGRA:
|
||||
case gpu::SBGRA:
|
||||
#if !defined(USE_GLES)
|
||||
texel.format = GL_BGRA;
|
||||
break;
|
||||
#endif
|
||||
case gpu::RGB:
|
||||
case gpu::RGBA:
|
||||
case gpu::SRGB:
|
||||
|
@ -478,6 +550,7 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
|
|||
texel.type = ELEMENT_TYPE_TO_GL[dstFormat.getType()];
|
||||
|
||||
switch (dstFormat.getSemantic()) {
|
||||
|
||||
case gpu::RED:
|
||||
case gpu::RGB:
|
||||
case gpu::RGBA:
|
||||
|
@ -788,8 +861,10 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E
|
|||
switch (srcFormat.getSemantic()) {
|
||||
case gpu::BGRA:
|
||||
case gpu::SBGRA:
|
||||
#if !defined(USE_GLES)
|
||||
texel.format = GL_BGRA;
|
||||
break;
|
||||
#endif
|
||||
case gpu::RGB:
|
||||
case gpu::RGBA:
|
||||
case gpu::SRGB:
|
|
@ -1,6 +1,6 @@
|
|||
set(TARGET_NAME gpu-gl)
|
||||
setup_hifi_library(Concurrent)
|
||||
link_hifi_libraries(shared gl gpu)
|
||||
link_hifi_libraries(shared gl gpu gpu-gl-common)
|
||||
if (UNIX)
|
||||
target_link_libraries(${TARGET_NAME} pthread)
|
||||
endif(UNIX)
|
||||
|
|
|
@ -1,772 +0,0 @@
|
|||
//
|
||||
// GLBackend.cpp
|
||||
// libraries/gpu/src/gpu
|
||||
//
|
||||
// Created by Sam Gateau on 10/27/2014.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "GLBackend.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <list>
|
||||
#include <functional>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
|
||||
#include "../gl41/GL41Backend.h"
|
||||
#include "../gl45/GL45Backend.h"
|
||||
|
||||
#if defined(NSIGHT_FOUND)
|
||||
#include "nvToolsExt.h"
|
||||
#endif
|
||||
|
||||
#include <shared/GlobalAppProperties.h>
|
||||
#include <GPUIdent.h>
|
||||
#include <gl/QOpenGLContextWrapper.h>
|
||||
#include <QtCore/QProcessEnvironment>
|
||||
|
||||
#include "GLTexture.h"
|
||||
#include "GLShader.h"
|
||||
|
||||
using namespace gpu;
|
||||
using namespace gpu::gl;
|
||||
|
||||
static const QString DEBUG_FLAG("HIFI_DISABLE_OPENGL_45");
|
||||
static bool disableOpenGL45 = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG);
|
||||
|
||||
static GLBackend* INSTANCE{ nullptr };
|
||||
|
||||
BackendPointer GLBackend::createBackend() {
|
||||
// FIXME provide a mechanism to override the backend for testing
|
||||
// Where the gpuContext is initialized and where the TRUE Backend is created and assigned
|
||||
auto version = QOpenGLContextWrapper::currentContextVersion();
|
||||
std::shared_ptr<GLBackend> result;
|
||||
if (!disableOpenGL45 && version >= 0x0405) {
|
||||
qCDebug(gpugllogging) << "Using OpenGL 4.5 backend";
|
||||
result = std::make_shared<gpu::gl45::GL45Backend>();
|
||||
} else {
|
||||
qCDebug(gpugllogging) << "Using OpenGL 4.1 backend";
|
||||
result = std::make_shared<gpu::gl41::GL41Backend>();
|
||||
}
|
||||
result->initInput();
|
||||
result->initTransform();
|
||||
result->initTextureManagementStage();
|
||||
|
||||
INSTANCE = result.get();
|
||||
void* voidInstance = &(*result);
|
||||
qApp->setProperty(hifi::properties::gl::BACKEND, QVariant::fromValue(voidInstance));
|
||||
return result;
|
||||
}
|
||||
|
||||
GLBackend& getBackend() {
|
||||
if (!INSTANCE) {
|
||||
INSTANCE = static_cast<GLBackend*>(qApp->property(hifi::properties::gl::BACKEND).value<void*>());
|
||||
}
|
||||
return *INSTANCE;
|
||||
}
|
||||
|
||||
bool GLBackend::makeProgram(Shader& shader, const Shader::BindingSet& slotBindings, const Shader::CompilationHandler& handler) {
|
||||
return GLShader::makeProgram(getBackend(), shader, slotBindings, handler);
|
||||
}
|
||||
|
||||
GLBackend::CommandCall GLBackend::_commandCalls[Batch::NUM_COMMANDS] =
|
||||
{
|
||||
(&::gpu::gl::GLBackend::do_draw),
|
||||
(&::gpu::gl::GLBackend::do_drawIndexed),
|
||||
(&::gpu::gl::GLBackend::do_drawInstanced),
|
||||
(&::gpu::gl::GLBackend::do_drawIndexedInstanced),
|
||||
(&::gpu::gl::GLBackend::do_multiDrawIndirect),
|
||||
(&::gpu::gl::GLBackend::do_multiDrawIndexedIndirect),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_setInputFormat),
|
||||
(&::gpu::gl::GLBackend::do_setInputBuffer),
|
||||
(&::gpu::gl::GLBackend::do_setIndexBuffer),
|
||||
(&::gpu::gl::GLBackend::do_setIndirectBuffer),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_setModelTransform),
|
||||
(&::gpu::gl::GLBackend::do_setViewTransform),
|
||||
(&::gpu::gl::GLBackend::do_setProjectionTransform),
|
||||
(&::gpu::gl::GLBackend::do_setViewportTransform),
|
||||
(&::gpu::gl::GLBackend::do_setDepthRangeTransform),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_setPipeline),
|
||||
(&::gpu::gl::GLBackend::do_setStateBlendFactor),
|
||||
(&::gpu::gl::GLBackend::do_setStateScissorRect),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_setUniformBuffer),
|
||||
(&::gpu::gl::GLBackend::do_setResourceBuffer),
|
||||
(&::gpu::gl::GLBackend::do_setResourceTexture),
|
||||
(&::gpu::gl::GLBackend::do_setResourceFramebufferSwapChainTexture),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_setFramebuffer),
|
||||
(&::gpu::gl::GLBackend::do_setFramebufferSwapChain),
|
||||
(&::gpu::gl::GLBackend::do_clearFramebuffer),
|
||||
(&::gpu::gl::GLBackend::do_blit),
|
||||
(&::gpu::gl::GLBackend::do_generateTextureMips),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_advance),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_beginQuery),
|
||||
(&::gpu::gl::GLBackend::do_endQuery),
|
||||
(&::gpu::gl::GLBackend::do_getQuery),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_resetStages),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_disableContextViewCorrection),
|
||||
(&::gpu::gl::GLBackend::do_restoreContextViewCorrection),
|
||||
(&::gpu::gl::GLBackend::do_disableContextStereo),
|
||||
(&::gpu::gl::GLBackend::do_restoreContextStereo),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_runLambda),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_startNamedCall),
|
||||
(&::gpu::gl::GLBackend::do_stopNamedCall),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_glUniform1i),
|
||||
(&::gpu::gl::GLBackend::do_glUniform1f),
|
||||
(&::gpu::gl::GLBackend::do_glUniform2f),
|
||||
(&::gpu::gl::GLBackend::do_glUniform3f),
|
||||
(&::gpu::gl::GLBackend::do_glUniform4f),
|
||||
(&::gpu::gl::GLBackend::do_glUniform3fv),
|
||||
(&::gpu::gl::GLBackend::do_glUniform4fv),
|
||||
(&::gpu::gl::GLBackend::do_glUniform4iv),
|
||||
(&::gpu::gl::GLBackend::do_glUniformMatrix3fv),
|
||||
(&::gpu::gl::GLBackend::do_glUniformMatrix4fv),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_glColor4f),
|
||||
|
||||
(&::gpu::gl::GLBackend::do_pushProfileRange),
|
||||
(&::gpu::gl::GLBackend::do_popProfileRange),
|
||||
};
|
||||
|
||||
void GLBackend::init() {
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [] {
|
||||
QString vendor{ (const char*)glGetString(GL_VENDOR) };
|
||||
QString renderer{ (const char*)glGetString(GL_RENDERER) };
|
||||
qCDebug(gpugllogging) << "GL Version: " << QString((const char*) glGetString(GL_VERSION));
|
||||
qCDebug(gpugllogging) << "GL Shader Language Version: " << QString((const char*) glGetString(GL_SHADING_LANGUAGE_VERSION));
|
||||
qCDebug(gpugllogging) << "GL Vendor: " << vendor;
|
||||
qCDebug(gpugllogging) << "GL Renderer: " << renderer;
|
||||
GPUIdent* gpu = GPUIdent::getInstance(vendor, renderer);
|
||||
// From here on, GPUIdent::getInstance()->getMumble() should efficiently give the same answers.
|
||||
qCDebug(gpugllogging) << "GPU:";
|
||||
qCDebug(gpugllogging) << "\tcard:" << gpu->getName();
|
||||
qCDebug(gpugllogging) << "\tdriver:" << gpu->getDriver();
|
||||
qCDebug(gpugllogging) << "\tdedicated memory:" << gpu->getMemory() << "MB";
|
||||
qCDebug(gpugllogging, "V-Sync is %s\n", (::gl::getSwapInterval() > 0 ? "ON" : "OFF"));
|
||||
#if THREADED_TEXTURE_BUFFERING
|
||||
// This has to happen on the main thread in order to give the thread
|
||||
// pool a reasonable parent object
|
||||
GLVariableAllocationSupport::TransferJob::startBufferingThread();
|
||||
#endif
|
||||
});
|
||||
}
|
||||
|
||||
GLBackend::GLBackend() {
|
||||
_pipeline._cameraCorrectionBuffer._buffer->flush();
|
||||
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &_uboAlignment);
|
||||
}
|
||||
|
||||
|
||||
GLBackend::~GLBackend() {
|
||||
killInput();
|
||||
killTransform();
|
||||
}
|
||||
|
||||
void GLBackend::renderPassTransfer(const Batch& batch) {
|
||||
const size_t numCommands = batch.getCommands().size();
|
||||
const Batch::Commands::value_type* command = batch.getCommands().data();
|
||||
const Batch::CommandOffsets::value_type* offset = batch.getCommandOffsets().data();
|
||||
|
||||
_inRenderTransferPass = true;
|
||||
{ // Sync all the buffers
|
||||
PROFILE_RANGE(render_gpu_gl_detail, "syncGPUBuffer");
|
||||
|
||||
for (auto& cached : batch._buffers._items) {
|
||||
if (cached._data) {
|
||||
syncGPUObject(*cached._data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{ // Sync all the transform states
|
||||
PROFILE_RANGE(render_gpu_gl_detail, "syncCPUTransform");
|
||||
_transform._cameras.clear();
|
||||
_transform._cameraOffsets.clear();
|
||||
|
||||
for (_commandIndex = 0; _commandIndex < numCommands; ++_commandIndex) {
|
||||
switch (*command) {
|
||||
case Batch::COMMAND_draw:
|
||||
case Batch::COMMAND_drawIndexed:
|
||||
case Batch::COMMAND_drawInstanced:
|
||||
case Batch::COMMAND_drawIndexedInstanced:
|
||||
case Batch::COMMAND_multiDrawIndirect:
|
||||
case Batch::COMMAND_multiDrawIndexedIndirect:
|
||||
_transform.preUpdate(_commandIndex, _stereo);
|
||||
break;
|
||||
|
||||
case Batch::COMMAND_disableContextStereo:
|
||||
_stereo._contextDisable = true;
|
||||
break;
|
||||
|
||||
case Batch::COMMAND_restoreContextStereo:
|
||||
_stereo._contextDisable = false;
|
||||
break;
|
||||
|
||||
case Batch::COMMAND_setViewportTransform:
|
||||
case Batch::COMMAND_setViewTransform:
|
||||
case Batch::COMMAND_setProjectionTransform: {
|
||||
CommandCall call = _commandCalls[(*command)];
|
||||
(this->*(call))(batch, *offset);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
command++;
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
{ // Sync the transform buffers
|
||||
PROFILE_RANGE(render_gpu_gl_detail, "syncGPUTransform");
|
||||
transferTransformState(batch);
|
||||
}
|
||||
|
||||
_inRenderTransferPass = false;
|
||||
}
|
||||
|
||||
void GLBackend::renderPassDraw(const Batch& batch) {
|
||||
_currentDraw = -1;
|
||||
_transform._camerasItr = _transform._cameraOffsets.begin();
|
||||
const size_t numCommands = batch.getCommands().size();
|
||||
const Batch::Commands::value_type* command = batch.getCommands().data();
|
||||
const Batch::CommandOffsets::value_type* offset = batch.getCommandOffsets().data();
|
||||
for (_commandIndex = 0; _commandIndex < numCommands; ++_commandIndex) {
|
||||
switch (*command) {
|
||||
// Ignore these commands on this pass, taken care of in the transfer pass
|
||||
// Note we allow COMMAND_setViewportTransform to occur in both passes
|
||||
// as it both updates the transform object (and thus the uniforms in the
|
||||
// UBO) as well as executes the actual viewport call
|
||||
case Batch::COMMAND_setModelTransform:
|
||||
case Batch::COMMAND_setViewTransform:
|
||||
case Batch::COMMAND_setProjectionTransform:
|
||||
break;
|
||||
|
||||
case Batch::COMMAND_draw:
|
||||
case Batch::COMMAND_drawIndexed:
|
||||
case Batch::COMMAND_drawInstanced:
|
||||
case Batch::COMMAND_drawIndexedInstanced:
|
||||
case Batch::COMMAND_multiDrawIndirect:
|
||||
case Batch::COMMAND_multiDrawIndexedIndirect: {
|
||||
// updates for draw calls
|
||||
++_currentDraw;
|
||||
updateInput();
|
||||
updateTransform(batch);
|
||||
updatePipeline();
|
||||
|
||||
CommandCall call = _commandCalls[(*command)];
|
||||
(this->*(call))(batch, *offset);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
CommandCall call = _commandCalls[(*command)];
|
||||
(this->*(call))(batch, *offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
command++;
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
void GLBackend::render(const Batch& batch) {
|
||||
_transform._skybox = _stereo._skybox = batch.isSkyboxEnabled();
|
||||
// Allow the batch to override the rendering stereo settings
|
||||
// for things like full framebuffer copy operations (deferred lighting passes)
|
||||
bool savedStereo = _stereo._enable;
|
||||
if (!batch.isStereoEnabled()) {
|
||||
_stereo._enable = false;
|
||||
}
|
||||
|
||||
{
|
||||
PROFILE_RANGE(render_gpu_gl_detail, "Transfer");
|
||||
renderPassTransfer(batch);
|
||||
}
|
||||
|
||||
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
|
||||
if (_stereo.isStereo()) {
|
||||
glEnable(GL_CLIP_DISTANCE0);
|
||||
}
|
||||
#endif
|
||||
{
|
||||
PROFILE_RANGE(render_gpu_gl_detail, _stereo.isStereo() ? "Render Stereo" : "Render");
|
||||
renderPassDraw(batch);
|
||||
}
|
||||
#ifdef GPU_STEREO_DRAWCALL_INSTANCED
|
||||
if (_stereo.isStereo()) {
|
||||
glDisable(GL_CLIP_DISTANCE0);
|
||||
}
|
||||
#endif
|
||||
// Restore the saved stereo state for the next batch
|
||||
_stereo._enable = savedStereo;
|
||||
}
|
||||
|
||||
|
||||
void GLBackend::syncCache() {
|
||||
PROFILE_RANGE(render_gpu_gl_detail, __FUNCTION__);
|
||||
|
||||
syncTransformStateCache();
|
||||
syncPipelineStateCache();
|
||||
syncInputStateCache();
|
||||
syncOutputStateCache();
|
||||
}
|
||||
|
||||
#ifdef GPU_STEREO_DRAWCALL_DOUBLED
|
||||
void GLBackend::setupStereoSide(int side) {
|
||||
ivec4 vp = _transform._viewport;
|
||||
vp.z /= 2;
|
||||
glViewport(vp.x + side * vp.z, vp.y, vp.z, vp.w);
|
||||
|
||||
|
||||
#ifdef GPU_STEREO_CAMERA_BUFFER
|
||||
#ifdef GPU_STEREO_DRAWCALL_DOUBLED
|
||||
glVertexAttribI1i(14, side);
|
||||
#endif
|
||||
#else
|
||||
_transform.bindCurrentCamera(side);
|
||||
#endif
|
||||
|
||||
}
|
||||
#else
|
||||
#endif
|
||||
|
||||
void GLBackend::do_resetStages(const Batch& batch, size_t paramOffset) {
|
||||
resetStages();
|
||||
}
|
||||
|
||||
void GLBackend::do_disableContextViewCorrection(const Batch& batch, size_t paramOffset) {
|
||||
_transform._viewCorrectionEnabled = false;
|
||||
}
|
||||
|
||||
void GLBackend::do_restoreContextViewCorrection(const Batch& batch, size_t paramOffset) {
|
||||
_transform._viewCorrectionEnabled = true;
|
||||
}
|
||||
|
||||
void GLBackend::do_disableContextStereo(const Batch& batch, size_t paramOffset) {
|
||||
|
||||
}
|
||||
|
||||
void GLBackend::do_restoreContextStereo(const Batch& batch, size_t paramOffset) {
|
||||
|
||||
}
|
||||
|
||||
void GLBackend::do_runLambda(const Batch& batch, size_t paramOffset) {
|
||||
std::function<void()> f = batch._lambdas.get(batch._params[paramOffset]._uint);
|
||||
f();
|
||||
}
|
||||
|
||||
void GLBackend::do_startNamedCall(const Batch& batch, size_t paramOffset) {
|
||||
batch._currentNamedCall = batch._names.get(batch._params[paramOffset]._uint);
|
||||
_currentDraw = -1;
|
||||
}
|
||||
|
||||
void GLBackend::do_stopNamedCall(const Batch& batch, size_t paramOffset) {
|
||||
batch._currentNamedCall.clear();
|
||||
}
|
||||
|
||||
void GLBackend::resetStages() {
|
||||
resetInputStage();
|
||||
resetPipelineStage();
|
||||
resetTransformStage();
|
||||
resetUniformStage();
|
||||
resetResourceStage();
|
||||
resetOutputStage();
|
||||
resetQueryStage();
|
||||
|
||||
(void) CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
|
||||
void GLBackend::do_pushProfileRange(const Batch& batch, size_t paramOffset) {
|
||||
if (trace_render_gpu_gl_detail().isDebugEnabled()) {
|
||||
auto name = batch._profileRanges.get(batch._params[paramOffset]._uint);
|
||||
profileRanges.push_back(name);
|
||||
#if defined(NSIGHT_FOUND)
|
||||
nvtxRangePush(name.c_str());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void GLBackend::do_popProfileRange(const Batch& batch, size_t paramOffset) {
|
||||
if (trace_render_gpu_gl_detail().isDebugEnabled()) {
|
||||
profileRanges.pop_back();
|
||||
#if defined(NSIGHT_FOUND)
|
||||
nvtxRangePop();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: As long as we have gl calls explicitely issued from interface
|
||||
// code, we need to be able to record and batch these calls. THe long
|
||||
// term strategy is to get rid of any GL calls in favor of the HIFI GPU API
|
||||
|
||||
// As long as we don;t use several versions of shaders we can avoid this more complex code path
|
||||
#ifdef GPU_STEREO_CAMERA_BUFFER
|
||||
#define GET_UNIFORM_LOCATION(shaderUniformLoc) ((_pipeline._programShader) ? _pipeline._programShader->getUniformLocation(shaderUniformLoc, (GLShader::Version) isStereo()) : -1)
|
||||
#else
|
||||
#define GET_UNIFORM_LOCATION(shaderUniformLoc) shaderUniformLoc
|
||||
#endif
|
||||
|
||||
void GLBackend::do_glUniform1i(const Batch& batch, size_t paramOffset) {
|
||||
if (_pipeline._program == 0) {
|
||||
// We should call updatePipeline() to bind the program but we are not doing that
|
||||
// because these uniform setters are deprecated and we don;t want to create side effect
|
||||
return;
|
||||
}
|
||||
updatePipeline();
|
||||
|
||||
glUniform1i(
|
||||
GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._int),
|
||||
batch._params[paramOffset + 0]._int);
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::do_glUniform1f(const Batch& batch, size_t paramOffset) {
|
||||
if (_pipeline._program == 0) {
|
||||
// We should call updatePipeline() to bind the program but we are not doing that
|
||||
// because these uniform setters are deprecated and we don;t want to create side effect
|
||||
return;
|
||||
}
|
||||
updatePipeline();
|
||||
|
||||
glUniform1f(
|
||||
GET_UNIFORM_LOCATION(batch._params[paramOffset + 1]._int),
|
||||
batch._params[paramOffset + 0]._float);
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::do_glUniform2f(const Batch& batch, size_t paramOffset) {
|
||||
if (_pipeline._program == 0) {
|
||||
// We should call updatePipeline() to bind the program but we are not doing that
|
||||
// because these uniform setters are deprecated and we don;t want to create side effect
|
||||
return;
|
||||
}
|
||||
updatePipeline();
|
||||
glUniform2f(
|
||||
GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int),
|
||||
batch._params[paramOffset + 1]._float,
|
||||
batch._params[paramOffset + 0]._float);
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::do_glUniform3f(const Batch& batch, size_t paramOffset) {
|
||||
if (_pipeline._program == 0) {
|
||||
// We should call updatePipeline() to bind the program but we are not doing that
|
||||
// because these uniform setters are deprecated and we don;t want to create side effect
|
||||
return;
|
||||
}
|
||||
updatePipeline();
|
||||
glUniform3f(
|
||||
GET_UNIFORM_LOCATION(batch._params[paramOffset + 3]._int),
|
||||
batch._params[paramOffset + 2]._float,
|
||||
batch._params[paramOffset + 1]._float,
|
||||
batch._params[paramOffset + 0]._float);
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::do_glUniform4f(const Batch& batch, size_t paramOffset) {
|
||||
if (_pipeline._program == 0) {
|
||||
// We should call updatePipeline() to bind the program but we are not doing that
|
||||
// because these uniform setters are deprecated and we don;t want to create side effect
|
||||
return;
|
||||
}
|
||||
updatePipeline();
|
||||
glUniform4f(
|
||||
GET_UNIFORM_LOCATION(batch._params[paramOffset + 4]._int),
|
||||
batch._params[paramOffset + 3]._float,
|
||||
batch._params[paramOffset + 2]._float,
|
||||
batch._params[paramOffset + 1]._float,
|
||||
batch._params[paramOffset + 0]._float);
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::do_glUniform3fv(const Batch& batch, size_t paramOffset) {
|
||||
if (_pipeline._program == 0) {
|
||||
// We should call updatePipeline() to bind the program but we are not doing that
|
||||
// because these uniform setters are deprecated and we don;t want to create side effect
|
||||
return;
|
||||
}
|
||||
updatePipeline();
|
||||
glUniform3fv(
|
||||
GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int),
|
||||
batch._params[paramOffset + 1]._uint,
|
||||
(const GLfloat*)batch.readData(batch._params[paramOffset + 0]._uint));
|
||||
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::do_glUniform4fv(const Batch& batch, size_t paramOffset) {
|
||||
if (_pipeline._program == 0) {
|
||||
// We should call updatePipeline() to bind the program but we are not doing that
|
||||
// because these uniform setters are deprecated and we don;t want to create side effect
|
||||
return;
|
||||
}
|
||||
updatePipeline();
|
||||
|
||||
GLint location = GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int);
|
||||
GLsizei count = batch._params[paramOffset + 1]._uint;
|
||||
const GLfloat* value = (const GLfloat*)batch.readData(batch._params[paramOffset + 0]._uint);
|
||||
glUniform4fv(location, count, value);
|
||||
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::do_glUniform4iv(const Batch& batch, size_t paramOffset) {
|
||||
if (_pipeline._program == 0) {
|
||||
// We should call updatePipeline() to bind the program but we are not doing that
|
||||
// because these uniform setters are deprecated and we don;t want to create side effect
|
||||
return;
|
||||
}
|
||||
updatePipeline();
|
||||
glUniform4iv(
|
||||
GET_UNIFORM_LOCATION(batch._params[paramOffset + 2]._int),
|
||||
batch._params[paramOffset + 1]._uint,
|
||||
(const GLint*)batch.readData(batch._params[paramOffset + 0]._uint));
|
||||
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::do_glUniformMatrix3fv(const Batch& batch, size_t paramOffset) {
|
||||
if (_pipeline._program == 0) {
|
||||
// We should call updatePipeline() to bind the program but we are not doing that
|
||||
// because these uniform setters are deprecated and we don;t want to create side effect
|
||||
return;
|
||||
}
|
||||
updatePipeline();
|
||||
|
||||
glUniformMatrix3fv(
|
||||
GET_UNIFORM_LOCATION(batch._params[paramOffset + 3]._int),
|
||||
batch._params[paramOffset + 2]._uint,
|
||||
batch._params[paramOffset + 1]._uint,
|
||||
(const GLfloat*)batch.readData(batch._params[paramOffset + 0]._uint));
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::do_glUniformMatrix4fv(const Batch& batch, size_t paramOffset) {
|
||||
if (_pipeline._program == 0) {
|
||||
// We should call updatePipeline() to bind the program but we are not doing that
|
||||
// because these uniform setters are deprecated and we don;t want to create side effect
|
||||
return;
|
||||
}
|
||||
updatePipeline();
|
||||
|
||||
glUniformMatrix4fv(
|
||||
GET_UNIFORM_LOCATION(batch._params[paramOffset + 3]._int),
|
||||
batch._params[paramOffset + 2]._uint,
|
||||
batch._params[paramOffset + 1]._uint,
|
||||
(const GLfloat*)batch.readData(batch._params[paramOffset + 0]._uint));
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::do_glColor4f(const Batch& batch, size_t paramOffset) {
|
||||
|
||||
glm::vec4 newColor(
|
||||
batch._params[paramOffset + 3]._float,
|
||||
batch._params[paramOffset + 2]._float,
|
||||
batch._params[paramOffset + 1]._float,
|
||||
batch._params[paramOffset + 0]._float);
|
||||
|
||||
if (_input._colorAttribute != newColor) {
|
||||
_input._colorAttribute = newColor;
|
||||
glVertexAttrib4fv(gpu::Stream::COLOR, &_input._colorAttribute.r);
|
||||
// Color has been changed and is not white. To prevent colors from bleeding
|
||||
// between different objects, we need to set the _hadColorAttribute flag
|
||||
// as if a previous render call had potential colors
|
||||
_input._hadColorAttribute = (newColor != glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
}
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::releaseBuffer(GLuint id, Size size) const {
|
||||
Lock lock(_trashMutex);
|
||||
_buffersTrash.push_back({ id, size });
|
||||
}
|
||||
|
||||
void GLBackend::releaseExternalTexture(GLuint id, const Texture::ExternalRecycler& recycler) const {
|
||||
Lock lock(_trashMutex);
|
||||
_externalTexturesTrash.push_back({ id, recycler });
|
||||
}
|
||||
|
||||
void GLBackend::releaseTexture(GLuint id, Size size) const {
|
||||
Lock lock(_trashMutex);
|
||||
_texturesTrash.push_back({ id, size });
|
||||
}
|
||||
|
||||
void GLBackend::releaseFramebuffer(GLuint id) const {
|
||||
Lock lock(_trashMutex);
|
||||
_framebuffersTrash.push_back(id);
|
||||
}
|
||||
|
||||
void GLBackend::releaseShader(GLuint id) const {
|
||||
Lock lock(_trashMutex);
|
||||
_shadersTrash.push_back(id);
|
||||
}
|
||||
|
||||
void GLBackend::releaseProgram(GLuint id) const {
|
||||
Lock lock(_trashMutex);
|
||||
_programsTrash.push_back(id);
|
||||
}
|
||||
|
||||
void GLBackend::releaseQuery(GLuint id) const {
|
||||
Lock lock(_trashMutex);
|
||||
_queriesTrash.push_back(id);
|
||||
}
|
||||
|
||||
void GLBackend::queueLambda(const std::function<void()> lambda) const {
|
||||
Lock lock(_trashMutex);
|
||||
_lambdaQueue.push_back(lambda);
|
||||
}
|
||||
|
||||
void GLBackend::recycle() const {
|
||||
PROFILE_RANGE(render_gpu_gl, __FUNCTION__)
|
||||
{
|
||||
std::list<std::function<void()>> lamdbasTrash;
|
||||
{
|
||||
Lock lock(_trashMutex);
|
||||
std::swap(_lambdaQueue, lamdbasTrash);
|
||||
}
|
||||
for (auto lambda : lamdbasTrash) {
|
||||
lambda();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<GLuint> ids;
|
||||
std::list<std::pair<GLuint, Size>> buffersTrash;
|
||||
{
|
||||
Lock lock(_trashMutex);
|
||||
std::swap(_buffersTrash, buffersTrash);
|
||||
}
|
||||
ids.reserve(buffersTrash.size());
|
||||
for (auto pair : buffersTrash) {
|
||||
ids.push_back(pair.first);
|
||||
}
|
||||
if (!ids.empty()) {
|
||||
glDeleteBuffers((GLsizei)ids.size(), ids.data());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<GLuint> ids;
|
||||
std::list<GLuint> framebuffersTrash;
|
||||
{
|
||||
Lock lock(_trashMutex);
|
||||
std::swap(_framebuffersTrash, framebuffersTrash);
|
||||
}
|
||||
ids.reserve(framebuffersTrash.size());
|
||||
for (auto id : framebuffersTrash) {
|
||||
ids.push_back(id);
|
||||
}
|
||||
if (!ids.empty()) {
|
||||
glDeleteFramebuffers((GLsizei)ids.size(), ids.data());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<GLuint> ids;
|
||||
std::list<std::pair<GLuint, Size>> texturesTrash;
|
||||
{
|
||||
Lock lock(_trashMutex);
|
||||
std::swap(_texturesTrash, texturesTrash);
|
||||
}
|
||||
ids.reserve(texturesTrash.size());
|
||||
for (auto pair : texturesTrash) {
|
||||
ids.push_back(pair.first);
|
||||
}
|
||||
if (!ids.empty()) {
|
||||
glDeleteTextures((GLsizei)ids.size(), ids.data());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::list<std::pair<GLuint, Texture::ExternalRecycler>> externalTexturesTrash;
|
||||
{
|
||||
Lock lock(_trashMutex);
|
||||
std::swap(_externalTexturesTrash, externalTexturesTrash);
|
||||
}
|
||||
if (!externalTexturesTrash.empty()) {
|
||||
std::vector<GLsync> fences;
|
||||
fences.resize(externalTexturesTrash.size());
|
||||
for (size_t i = 0; i < externalTexturesTrash.size(); ++i) {
|
||||
fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
}
|
||||
// External texture fences will be read in another thread/context, so we need a flush
|
||||
glFlush();
|
||||
size_t index = 0;
|
||||
for (auto pair : externalTexturesTrash) {
|
||||
auto fence = fences[index++];
|
||||
pair.second(pair.first, fence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::list<GLuint> programsTrash;
|
||||
{
|
||||
Lock lock(_trashMutex);
|
||||
std::swap(_programsTrash, programsTrash);
|
||||
}
|
||||
for (auto id : programsTrash) {
|
||||
glDeleteProgram(id);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::list<GLuint> shadersTrash;
|
||||
{
|
||||
Lock lock(_trashMutex);
|
||||
std::swap(_shadersTrash, shadersTrash);
|
||||
}
|
||||
for (auto id : shadersTrash) {
|
||||
glDeleteShader(id);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<GLuint> ids;
|
||||
std::list<GLuint> queriesTrash;
|
||||
{
|
||||
Lock lock(_trashMutex);
|
||||
std::swap(_queriesTrash, queriesTrash);
|
||||
}
|
||||
ids.reserve(queriesTrash.size());
|
||||
for (auto id : queriesTrash) {
|
||||
ids.push_back(id);
|
||||
}
|
||||
if (!ids.empty()) {
|
||||
glDeleteQueries((GLsizei)ids.size(), ids.data());
|
||||
}
|
||||
}
|
||||
|
||||
GLVariableAllocationSupport::manageMemory();
|
||||
GLVariableAllocationSupport::_frameTexturesCreated = 0;
|
||||
Texture::KtxStorage::releaseOpenKtxFiles();
|
||||
}
|
||||
|
||||
void GLBackend::setCameraCorrection(const Mat4& correction, const Mat4& prevRenderView, bool reset) {
|
||||
auto invCorrection = glm::inverse(correction);
|
||||
auto invPrevView = glm::inverse(prevRenderView);
|
||||
_transform._correction.prevView = (reset ? Mat4() : prevRenderView);
|
||||
_transform._correction.prevViewInverse = (reset ? Mat4() : invPrevView);
|
||||
_transform._correction.correction = correction;
|
||||
_transform._correction.correctionInverse = invCorrection;
|
||||
_pipeline._cameraCorrectionBuffer._buffer->setSubData(0, _transform._correction);
|
||||
_pipeline._cameraCorrectionBuffer._buffer->flush();
|
||||
}
|
|
@ -1,157 +0,0 @@
|
|||
//
|
||||
// GLBackendInput.cpp
|
||||
// libraries/gpu/src/gpu
|
||||
//
|
||||
// Created by Sam Gateau on 3/8/2015.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include "GLBackend.h"
|
||||
#include "GLShared.h"
|
||||
#include "GLInputFormat.h"
|
||||
|
||||
using namespace gpu;
|
||||
using namespace gpu::gl;
|
||||
|
||||
void GLBackend::do_setInputFormat(const Batch& batch, size_t paramOffset) {
|
||||
Stream::FormatPointer format = batch._streamFormats.get(batch._params[paramOffset]._uint);
|
||||
if (format != _input._format) {
|
||||
_input._format = format;
|
||||
if (format) {
|
||||
auto inputFormat = GLInputFormat::sync((*format));
|
||||
assert(inputFormat);
|
||||
if (_input._formatKey != inputFormat->key) {
|
||||
_input._formatKey = inputFormat->key;
|
||||
_input._invalidFormat = true;
|
||||
}
|
||||
} else {
|
||||
_input._formatKey.clear();
|
||||
_input._invalidFormat = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLBackend::do_setInputBuffer(const Batch& batch, size_t paramOffset) {
|
||||
Offset stride = batch._params[paramOffset + 0]._uint;
|
||||
Offset offset = batch._params[paramOffset + 1]._uint;
|
||||
BufferPointer buffer = batch._buffers.get(batch._params[paramOffset + 2]._uint);
|
||||
uint32 channel = batch._params[paramOffset + 3]._uint;
|
||||
|
||||
if (channel < getNumInputBuffers()) {
|
||||
bool isModified = false;
|
||||
if (_input._buffers[channel] != buffer) {
|
||||
_input._buffers[channel] = buffer;
|
||||
|
||||
GLuint vbo = 0;
|
||||
if (buffer) {
|
||||
vbo = getBufferID((*buffer));
|
||||
}
|
||||
_input._bufferVBOs[channel] = vbo;
|
||||
|
||||
isModified = true;
|
||||
}
|
||||
|
||||
if (_input._bufferOffsets[channel] != offset) {
|
||||
_input._bufferOffsets[channel] = offset;
|
||||
isModified = true;
|
||||
}
|
||||
|
||||
if (_input._bufferStrides[channel] != stride) {
|
||||
_input._bufferStrides[channel] = stride;
|
||||
isModified = true;
|
||||
}
|
||||
|
||||
if (isModified) {
|
||||
_input._invalidBuffers.set(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLBackend::initInput() {
|
||||
if(!_input._defaultVAO) {
|
||||
glGenVertexArrays(1, &_input._defaultVAO);
|
||||
}
|
||||
glBindVertexArray(_input._defaultVAO);
|
||||
(void) CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::killInput() {
|
||||
glBindVertexArray(0);
|
||||
if(_input._defaultVAO) {
|
||||
glDeleteVertexArrays(1, &_input._defaultVAO);
|
||||
}
|
||||
(void) CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::syncInputStateCache() {
|
||||
for (uint32_t i = 0; i < _input._attributeActivation.size(); i++) {
|
||||
GLint active = 0;
|
||||
glGetVertexAttribiv(i, GL_VERTEX_ATTRIB_ARRAY_ENABLED, &active);
|
||||
_input._attributeActivation[i] = active;
|
||||
}
|
||||
//_input._defaultVAO
|
||||
glBindVertexArray(_input._defaultVAO);
|
||||
}
|
||||
|
||||
void GLBackend::resetInputStage() {
|
||||
// Reset index buffer
|
||||
_input._indexBufferType = UINT32;
|
||||
_input._indexBufferOffset = 0;
|
||||
_input._indexBuffer.reset();
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
(void) CHECK_GL_ERROR();
|
||||
|
||||
// Reset vertex buffer and format
|
||||
_input._format.reset();
|
||||
_input._formatKey.clear();
|
||||
_input._invalidFormat = false;
|
||||
_input._attributeActivation.reset();
|
||||
|
||||
for (uint32_t i = 0; i < _input._buffers.size(); i++) {
|
||||
_input._buffers[i].reset();
|
||||
_input._bufferOffsets[i] = 0;
|
||||
_input._bufferStrides[i] = 0;
|
||||
_input._bufferVBOs[i] = 0;
|
||||
}
|
||||
_input._invalidBuffers.reset();
|
||||
|
||||
// THe vertex array binding MUST be reset in the specific Backend versions as they use different techniques
|
||||
}
|
||||
|
||||
void GLBackend::do_setIndexBuffer(const Batch& batch, size_t paramOffset) {
|
||||
_input._indexBufferType = (Type)batch._params[paramOffset + 2]._uint;
|
||||
_input._indexBufferOffset = batch._params[paramOffset + 0]._uint;
|
||||
|
||||
BufferPointer indexBuffer = batch._buffers.get(batch._params[paramOffset + 1]._uint);
|
||||
if (indexBuffer != _input._indexBuffer) {
|
||||
_input._indexBuffer = indexBuffer;
|
||||
if (indexBuffer) {
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, getBufferID(*indexBuffer));
|
||||
} else {
|
||||
// FIXME do we really need this? Is there ever a draw call where we care that the element buffer is null?
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
}
|
||||
}
|
||||
(void) CHECK_GL_ERROR();
|
||||
}
|
||||
|
||||
void GLBackend::do_setIndirectBuffer(const Batch& batch, size_t paramOffset) {
|
||||
_input._indirectBufferOffset = batch._params[paramOffset + 1]._uint;
|
||||
_input._indirectBufferStride = batch._params[paramOffset + 2]._uint;
|
||||
|
||||
BufferPointer buffer = batch._buffers.get(batch._params[paramOffset]._uint);
|
||||
if (buffer != _input._indirectBuffer) {
|
||||
_input._indirectBuffer = buffer;
|
||||
if (buffer) {
|
||||
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, getBufferID(*buffer));
|
||||
} else {
|
||||
// FIXME do we really need this? Is there ever a draw call where we care that the element buffer is null?
|
||||
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, 0);
|
||||
}
|
||||
}
|
||||
|
||||
(void)CHECK_GL_ERROR();
|
||||
}
|
||||
|
63
libraries/gpu-gl/src/gpu/gl/GLDesktopBackend.cpp
Normal file
63
libraries/gpu-gl/src/gpu/gl/GLDesktopBackend.cpp
Normal file
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// GLBackend.cpp
|
||||
// libraries/gpu/src/gpu
|
||||
//
|
||||
// Created by Sam Gateau on 10/27/2014.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include <gpu/gl/GLBackend.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <QtCore/QProcessEnvironment>
|
||||
|
||||
#include <shared/GlobalAppProperties.h>
|
||||
#include <gl/QOpenGLContextWrapper.h>
|
||||
#include <gpu/gl/GLShader.h>
|
||||
|
||||
#include "../gl41/GL41Backend.h"
|
||||
#include "../gl45/GL45Backend.h"
|
||||
|
||||
using namespace gpu;
|
||||
using namespace gpu::gl;
|
||||
|
||||
static const QString DEBUG_FLAG("HIFI_DISABLE_OPENGL_45");
|
||||
static bool disableOpenGL45 = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG);
|
||||
|
||||
static GLBackend* INSTANCE{ nullptr };
|
||||
|
||||
BackendPointer GLBackend::createBackend() {
|
||||
// FIXME provide a mechanism to override the backend for testing
|
||||
// Where the gpuContext is initialized and where the TRUE Backend is created and assigned
|
||||
auto version = QOpenGLContextWrapper::currentContextVersion();
|
||||
std::shared_ptr<GLBackend> result;
|
||||
if (!disableOpenGL45 && version >= 0x0405) {
|
||||
qCDebug(gpugllogging) << "Using OpenGL 4.5 backend";
|
||||
result = std::make_shared<gpu::gl45::GL45Backend>();
|
||||
} else {
|
||||
qCDebug(gpugllogging) << "Using OpenGL 4.1 backend";
|
||||
result = std::make_shared<gpu::gl41::GL41Backend>();
|
||||
}
|
||||
result->initInput();
|
||||
result->initTransform();
|
||||
result->initTextureManagementStage();
|
||||
|
||||
INSTANCE = result.get();
|
||||
void* voidInstance = &(*result);
|
||||
qApp->setProperty(hifi::properties::gl::BACKEND, QVariant::fromValue(voidInstance));
|
||||
return result;
|
||||
}
|
||||
|
||||
GLBackend& getBackend() {
|
||||
if (!INSTANCE) {
|
||||
INSTANCE = static_cast<GLBackend*>(qApp->property(hifi::properties::gl::BACKEND).value<void*>());
|
||||
}
|
||||
return *INSTANCE;
|
||||
}
|
||||
|
||||
bool GLBackend::makeProgram(Shader& shader, const Shader::BindingSet& slotBindings, const Shader::CompilationHandler& handler) {
|
||||
return GLShader::makeProgram(getBackend(), shader, slotBindings, handler);
|
||||
}
|
|
@ -13,8 +13,8 @@
|
|||
|
||||
#include <gl/Config.h>
|
||||
|
||||
#include "../gl/GLBackend.h"
|
||||
#include "../gl/GLTexture.h"
|
||||
#include <gpu/gl/GLBackend.h>
|
||||
#include <gpu/gl/GLTexture.h>
|
||||
|
||||
#define GPU_CORE_41 410
|
||||
#define GPU_CORE_43 430
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue