Revision control

++ // Copyright (C) 2023 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ import QtQuick
++ import QtQuick.Controls
++ import QtQuick.Layouts
++ import QtQuick3D
++ import QtQuick3D.Helpers.impl
++
++ Pane {
++ id: root
++ property var source: null
++ property bool resourceDetailsVisible: false
++ opacity: 0.9
++
++ ColumnLayout {
++ id: layout
++ RowLayout {
++ Label {
++ Layout.fillWidth: true
++ text: root.source.renderStats.fps + " FPS"
++ font.pointSize: 14
++ }
++
++ Label {
++ text: "Details"
++ }
++
++ CheckBox {
++ checked: root.resourceDetailsVisible
++ onCheckedChanged: {
++ root.resourceDetailsVisible = checked;
++ }
++ }
++ }
++
++ component TimeLabel : RowLayout {
++ id: timeLabel
++ property alias text: label.text
++ property real value: 0.0
++ Label {
++ id: label
++ Layout.fillWidth: true
++ text: "Frame: "
++
++ }
++ Label {
++ text: timeLabel.value.toFixed(3) + "ms"
++ }
++ }
++
++ TimeLabel {
++ text: "Frame: "
++ value: root.source.renderStats.frameTime
++ }
++
++ TimeLabel {
++ text: " Sync: "
++ value: root.source.renderStats.syncTime
++ }
++
++ TimeLabel {
++ text: " Prep: "
++ value: root.source.renderStats.renderPrepareTime
++ }
++
++ TimeLabel {
++ text: " Render: "
++ value: root.source.renderStats.renderTime
++ }
++
++ TimeLabel {
++ text: "Max: "
++ value: root.source.renderStats.maxFrameTime
++ }
++
++ TimeLabel {
++ text: "GPU: "
++ value: root.source.renderStats.lastCompletedGpuTime
++ visible: root.source.renderStats.lastCompletedGpuTime > 0
++ }
++
++ Page {
++ Layout.fillWidth: true
++ Layout.minimumWidth: 530
++ visible: root.resourceDetailsVisible
++ header: TabBar {
++ id: tabBar
++ TabButton {
++ text: "Summary"
++ }
++ TabButton {
++ text: "Passes"
++ }
++ TabButton {
++ text: "Textures"
++ }
++ TabButton {
++ text: "Meshes"
++ }
++ TabButton {
++ text: "Tools"
++ }
++ TabButton {
++ text: "Shadows"
++ }
++ }
++
++ StackLayout {
++ anchors.fill: parent
++ anchors.margins: 10
++ currentIndex: tabBar.currentIndex
++
++ Pane {
++ id: summaryPane
++ ColumnLayout {
++ Label {
++ text: "Graphics API: " + root.source.renderStats.graphicsApiName
++ visible: root.resourceDetailsVisible
++ }
++ Label {
++ text: root.source.renderStats.renderPassCount + " render passes"
++ visible: root.resourceDetailsVisible
++ }
++ Label {
++ text: root.source.renderStats.drawCallCount + " draw calls"
++ visible: root.resourceDetailsVisible
++ }
++ Label {
++ text: root.source.renderStats.drawVertexCount + " vertices"
++ visible: root.resourceDetailsVisible
++ }
++ Label {
++ text: "Image assets: " + (root.source.renderStats.imageDataSize / 1024).toFixed(2) + " KB"
++ visible: root.resourceDetailsVisible
++ }
++ Label {
++ text: "Mesh assets: " + (root.source.renderStats.meshDataSize / 1024).toFixed(2) + " KB"
++ visible: root.resourceDetailsVisible
++ }
++ Label {
++ text: "Pipelines: " + root.source.renderStats.pipelineCount
++ visible: root.resourceDetailsVisible
++ }
++ Label {
++ text: "Material build time: " + root.source.renderStats.materialGenerationTime + " ms"
++ visible: root.resourceDetailsVisible
++ }
++ Label {
++ text: "Effect build time: " + root.source.renderStats.effectGenerationTime + " ms"
++ visible: root.resourceDetailsVisible
++ }
++ Label {
++ text: "Pipeline build time: " + root.source.renderStats.pipelineCreationTime + " ms"
++ visible: root.resourceDetailsVisible
++ }
++ Label {
++ text: root.source.renderStats.vmemAllocCount + " vmem allocs with " + root.source.renderStats.vmemUsedBytes + " bytes"
++ visible: root.resourceDetailsVisible && root.source.renderStats.vmemAllocCount > 0
++ }
++ }
++ }
++
++ Pane {
++ id: passesPane
++ RenderStatsPassesModel {
++ id: passesModel
++ passData: root.source.renderStats.renderPassDetails
++ }
++ ColumnLayout {
++ anchors.fill: parent
++ spacing: 0
++ HorizontalHeaderView {
++ syncView: passesTableView
++ resizableColumns: false // otherwise QTBUG-111013 happens
++ boundsBehavior: Flickable.StopAtBounds
++ flickableDirection: Flickable.VerticalFlick
++ }
++ ListModel {
++ id: passesHeaderModel
++ ListElement {
++ columnWidth: 300 // name
++ }
++ ListElement {
++ columnWidth: 80 // size
++ }
++ ListElement {
++ columnWidth: 60 // vertices
++ }
++ ListElement {
++ columnWidth: 60 // draw calls
++ }
++ }
++ Item {
++ Layout.fillHeight: true
++ Layout.fillWidth: true
++ TableView {
++ id: passesTableView
++ anchors.fill: parent
++ // name, size, vertices, draw calls
++ property var columnFactors: [58, 14, 12, 12]; // == 96, leave space for the scrollbar
++ columnWidthProvider: function (column) {
++ return passesPane.width * (columnFactors[column] / 100.0);
++ }
++ boundsBehavior: Flickable.StopAtBounds
++ flickableDirection: Flickable.VerticalFlick
++ ScrollBar.vertical: ScrollBar {
++ parent: passesTableView.parent
++ anchors.top: passesTableView.top
++ anchors.bottom: passesTableView.bottom
++ anchors.left: passesTableView.right
++ }
++ clip: true
++ model: passesModel
++ columnSpacing: 1
++ rowSpacing: 1
++ implicitWidth: parent.width + columnSpacing
++ implicitHeight: parent.height + rowSpacing
++ delegate: CustomTableItemDelegate {
++ required property string display
++ text: display
++ color: TableView.view.palette.base
++ textColor: TableView.view.palette.text
++ }
++ }
++ }
++ }
++ }
++
++ Pane {
++ id: texturesPane
++ RenderStatsTexturesModel {
++ id: texturesModel
++ textureData: root.source.renderStats.textureDetails
++ }
++ ColumnLayout {
++ anchors.fill: parent
++ spacing: 0
++ HorizontalHeaderView {
++ syncView: texturesTableView
++ resizableColumns: false // otherwise QTBUG-111013 happens
++ boundsBehavior: Flickable.StopAtBounds
++ flickableDirection: Flickable.VerticalFlick
++ }
++ Item {
++ Layout.fillHeight: true
++ Layout.fillWidth: true
++ TableView {
++ id: texturesTableView
++ anchors.fill: parent
++ // name, size, format, miplevels, flags
++ property var columnFactors: [48, 12, 12, 12, 12]; // == 96, leave space for the scrollbar
++ columnWidthProvider: function (column) {
++ return texturesPane.width * (columnFactors[column] / 100.0);
++ }
++ boundsBehavior: Flickable.StopAtBounds
++ flickableDirection: Flickable.VerticalFlick
++ ScrollBar.vertical: ScrollBar {
++ parent: texturesTableView.parent
++ anchors.top: texturesTableView.top
++ anchors.bottom: texturesTableView.bottom
++ anchors.left: texturesTableView.right
++ }
++ ScrollBar.horizontal: ScrollBar { }
++ clip: true
++ model: texturesModel
++ columnSpacing: 1
++ rowSpacing: 1
++ implicitWidth: parent.width + columnSpacing
++ implicitHeight: parent.height + rowSpacing
++ delegate: CustomTableItemDelegate {
++ required property string display
++ text: display
++ color: TableView.view.palette.base
++ textColor: TableView.view.palette.text
++ }
++ }
++ }
++ }
++ }
++
++ Pane {
++ id: meshesPane
++ RenderStatsMeshesModel {
++ id: meshesModel
++ meshData: root.source.renderStats.meshDetails
++ }
++ ColumnLayout {
++ anchors.fill: parent
++ spacing: 0
++ HorizontalHeaderView {
++ syncView: meshesTableView
++ resizableColumns: false // otherwise QTBUG-111013 happens
++ boundsBehavior: Flickable.StopAtBounds
++ flickableDirection: Flickable.VerticalFlick
++ }
++ Item {
++ Layout.fillHeight: true
++ Layout.fillWidth: true
++ TableView {
++ id: meshesTableView
++ anchors.fill: parent
++ // name, submeshes, vertices, vbufsize, ibufsize
++ property var columnFactors: [48, 12, 12, 12, 12]; // == 96, leave space for the scrollbar
++ columnWidthProvider: function (column) {
++ return meshesPane.width * (columnFactors[column] / 100.0);
++ }
++ boundsBehavior: Flickable.StopAtBounds
++ flickableDirection: Flickable.VerticalFlick
++ ScrollBar.vertical: ScrollBar {
++ parent: meshesTableView.parent
++ anchors.top: meshesTableView.top
++ anchors.bottom: meshesTableView.bottom
++ anchors.left: meshesTableView.right
++ }
++ clip: true
++ model: meshesModel
++ columnSpacing: 1
++ rowSpacing: 1
++ implicitWidth: parent.width + columnSpacing
++ implicitHeight: parent.height + rowSpacing
++ delegate: CustomTableItemDelegate {
++ required property string display
++ text: display
++ color: TableView.view.palette.base
++ textColor: TableView.view.palette.text
++ }
++ }
++ }
++ }
++ }
++
++ Pane {
++ id: visualizePane
++ ColumnLayout {
++ id: visCtrCol
++ width: parent.width
++ CheckBox {
++ text: "Wireframe mode"
++ onCheckedChanged: root.source.environment.debugSettings.wireframeEnabled = checked
++ }
++ RowLayout {
++ Label {
++ text: "Material override"
++ }
++ ComboBox {
++ id: materialOverrideComboBox
++ textRole: "text"
++ valueRole: "value"
++ implicitContentWidthPolicy: ComboBox.WidestText
++ onActivated: root.source.environment.debugSettings.materialOverride = currentValue
++ Component.onCompleted: materialOverrideComboBox.currentIndex = materialOverrideComboBox.indexOfValue(root.source.environment.debugSettings.materialOverride)
++ model: [
++ { value: DebugSettings.None, text: "None"},
++ { value: DebugSettings.BaseColor, text: "Base Color"},
++ { value: DebugSettings.Roughness, text: "Roughness"},
++ { value: DebugSettings.Metalness, text: "Metalness"},
++ { value: DebugSettings.Diffuse, text: "Diffuse"},
++ { value: DebugSettings.Specular, text: "Specular"},
++ { value: DebugSettings.ShadowOcclusion, text: "Shadow Occlusion"},
++ { value: DebugSettings.Emission, text: "Emission"},
++ { value: DebugSettings.AmbientOcclusion, text: "Ambient Occlusion"},
++ { value: DebugSettings.Normals, text: "Normals"},
++ { value: DebugSettings.Tangents, text: "Tangents"},
++ { value: DebugSettings.Binormals, text: "Binormals"},
++ { value: DebugSettings.F0, text: "F0"}
++ ]
++ }
++ }
++ RowLayout {
++ spacing: 20
++ Button {
++ text: "Release cached resources"
++ onClicked: root.source.renderStats.releaseCachedResources()
++ }
++ Button {
++ text: "Bake lightmap"
++ onClicked: root.source.bakeLightmap()
++ }
++ Button {
++ text: "Denoise lightmap"
++ onClicked: root.source.denoiseLightmap()
++ }
++ }
++ RowLayout {
++ Label {
++ text: "Render mode override"
++ }
++ ComboBox {
++ id: renderModeOverrideComboBox
++ textRole: "text"
++ valueRole: "value"
++ implicitContentWidthPolicy: ComboBox.WidestText
++ onActivated: root.source.renderMode = currentValue
++ Component.onCompleted: renderModeOverrideComboBox.currentIndex = renderModeOverrideComboBox.indexOfValue(root.source.renderMode)
++ model: [
++ { value: View3D.Offscreen, text: "Offscreen" },
++ { value: View3D.Underlay, text: "Underlay" },
++ { value: View3D.Overlay, text: "Overlay" },
++ { value: View3D.Inline, text: "Inline" }
++ ]
++ }
++ }
++
++ Label {
++ text: "View3D logical size is " + root.source.width + "x" + root.source.height
++ }
++ Label {
++ text: "Backing texture pixel size is " + root.source.effectiveTextureSize.width + "x" + root.source.effectiveTextureSize.height
++ visible: root.source.renderMode === View3D.Offscreen
++ }
++ RowLayout {
++ CheckBox {
++ id: explicitTextureSizeCheckBox
++ visible: root.source.renderMode === View3D.Offscreen
++ text: "Explicit backing texture size"
++ property real aspectRatio: root.source.width / root.source.height
++ onCheckedChanged: updateSize()
++ function updateSize() {
++ if (!explicitTextureSizeCheckBox.checked) {
++ root.source.explicitTextureWidth = 0;
++ root.source.explicitTextureHeight = 0;
++ return;
++ }
++ var newWidth = explicitWidthSlider.value;
++ var newHeight = explicitHeightSlider.value;
++ if (keepAspectRatioCheckBox.checked) {
++ var aspectRatio = explicitTextureSizeCheckBox.aspectRatio;
++ if (newHeight * aspectRatio <= newWidth)
++ newWidth = newHeight * aspectRatio;
++ else
++ newHeight = newWidth * (1.0 / aspectRatio);
++ }
++ root.source.explicitTextureWidth = newWidth;
++ root.source.explicitTextureHeight = newHeight;
++ }
++ Connections {
++ target: root.source
++ function onWidthChanged() { explicitTextureSizeCheckBox.updateSize() }
++ function onHeightChanged() { explicitTextureSizeCheckBox.updateSize() }
++ }
++ }
++ CheckBox {
++ id: keepAspectRatioCheckBox
++ visible: root.source.renderMode === View3D.Offscreen && explicitTextureSizeCheckBox.checked
++ text: "Keep aspect ratio (" + explicitTextureSizeCheckBox.aspectRatio.toFixed(2) + ")"
++ checked: false
++ onCheckedChanged: explicitTextureSizeCheckBox.updateSize()
++ }
++ }
++ RowLayout {
++ visible: root.source.renderMode === View3D.Offscreen && explicitTextureSizeCheckBox.checked
++ Label {
++ text: "Width: " + explicitWidthSlider.value.toFixed(0) + " px"
++ }
++ Slider {
++ id: explicitWidthSlider
++ from: 16
++ to: 4096
++ value: 1280
++ onValueChanged: explicitTextureSizeCheckBox.updateSize()
++ Layout.maximumWidth: 120
++ }
++ Label {
++ text: "Height: " + explicitHeightSlider.value.toFixed(0) + " px"
++ }
++ Slider {
++ id: explicitHeightSlider
++ from: 16
++ to: 4096
++ value: 720
++ onValueChanged: explicitTextureSizeCheckBox.updateSize()
++ Layout.maximumWidth: 120
++ }
++ }
++ }
++ }
++
++ Pane {
++ id: shadowsPane
++ ColumnLayout {
++ width: parent.width
++ CheckBox {
++ text: "Draw directional light shadow bounding boxes"
++ checked: root.source.environment.debugSettings.drawDirectionalLightShadowBoxes
++ onCheckedChanged: root.source.environment.debugSettings.drawDirectionalLightShadowBoxes = checked
++ }
++ CheckBox {
++ text: "Draw point light shadow bounding boxes"
++ checked: root.source.environment.debugSettings.drawPointLightShadowBoxes
++ onCheckedChanged: root.source.environment.debugSettings.drawPointLightShadowBoxes = checked
++ }
++ CheckBox {
++ text: "Draw shadow casting bounding box"
++ checked: root.source.environment.debugSettings.drawShadowCastingBounds
++ onCheckedChanged: root.source.environment.debugSettings.drawShadowCastingBounds = checked
++ }
++ CheckBox {
++ text: "Draw shadow receiving bounding box"
++ checked: root.source.environment.debugSettings.drawShadowReceivingBounds
++ onCheckedChanged: root.source.environment.debugSettings.drawShadowReceivingBounds = checked
++ }
++ CheckBox {
++ text: "Draw cascades"
++ checked: root.source.environment.debugSettings.drawCascades
++ onCheckedChanged: root.source.environment.debugSettings.drawCascades = checked
++ }
++ CheckBox {
++ text: "Draw scene cascade intersection"
++ checked: root.source.environment.debugSettings.drawSceneCascadeIntersection
++ onCheckedChanged: root.source.environment.debugSettings.drawSceneCascadeIntersection = checked
++ }
++ CheckBox {
++ text: "Disable Shadow Camera Update"
++ checked: root.source.environment.debugSettings.disableShadowCameraUpdate
++ onCheckedChanged: root.source.environment.debugSettings.disableShadowCameraUpdate = checked
++ }
++ CheckBox {
++ text: "Draw Culled Objects"
++ checked: root.source.environment.debugSettings.drawCulledObjects
++ onCheckedChanged: root.source.environment.debugSettings.drawCulledObjects = checked
++ }
++ }
++ }
++ }
++ }
++ }
++
++ component CustomTableItemDelegate : Rectangle {
++ property alias text: textLabel.text
++ property alias textColor: textLabel.color
++ implicitWidth: 100
++ implicitHeight: textLabel.implicitHeight + 4
++ color: palette.base
++ Label {
++ id: textLabel
++ anchors.centerIn: parent
++ color: palette.text
++ }
++ }
++
++ function syncVisible() {
++ if (source) {
++ source.renderStats.extendedDataCollectionEnabled = visible && resourceDetailsVisible;
++ if (source.renderStats.extendedDataCollectionEnabled)
++ source.update();
++ }
++ }
++
++ Component.onCompleted: syncVisible()
++ onSourceChanged: syncVisible()
++ onVisibleChanged: syncVisible()
++ onResourceDetailsVisibleChanged: syncVisible()
++ }