diff --git a/.gitignore b/.gitignore index 54257b56b5..4939060dce 100644 --- a/.gitignore +++ b/.gitignore @@ -10,16 +10,19 @@ *.pyc makefile *.DS_Store - +*.lck +*/genfiles.properties .v build dist endorsed +cache /nbproject/private /*/nbproject/private */.jacocoverage/ jacoco* +ConstellationBootstrap CoreDependencies/release/modules/ext CoreDependencies/nbproject/project_old.xml diff --git a/CoreAnalyticSchema/src/au/gov/asd/tac/constellation/graph/schema/analytic/utilities/VertexDominanceCalculator.java b/CoreAnalyticSchema/src/au/gov/asd/tac/constellation/graph/schema/analytic/utilities/VertexDominanceCalculator.java index 22556a23ea..5175077511 100644 --- a/CoreAnalyticSchema/src/au/gov/asd/tac/constellation/graph/schema/analytic/utilities/VertexDominanceCalculator.java +++ b/CoreAnalyticSchema/src/au/gov/asd/tac/constellation/graph/schema/analytic/utilities/VertexDominanceCalculator.java @@ -30,6 +30,7 @@ * * @author cygnus_x-1 */ +@SuppressWarnings("rawtypes") public abstract class VertexDominanceCalculator { public static VertexDominanceCalculator getDefault() { diff --git a/CoreAnalyticView/src/au/gov/asd/tac/constellation/views/analyticview/AnalyticConfigurationPane.java b/CoreAnalyticView/src/au/gov/asd/tac/constellation/views/analyticview/AnalyticConfigurationPane.java index 74e57e8a10..3128408f1e 100644 --- a/CoreAnalyticView/src/au/gov/asd/tac/constellation/views/analyticview/AnalyticConfigurationPane.java +++ b/CoreAnalyticView/src/au/gov/asd/tac/constellation/views/analyticview/AnalyticConfigurationPane.java @@ -101,6 +101,7 @@ public class AnalyticConfigurationPane extends VBox { private final Map, List> questionToPluginsMap; private final PluginParameters globalAnalyticParameters = new PluginParameters(); + @SuppressWarnings({"unchecked", "rawtypes"}) public AnalyticConfigurationPane() { // create the parameters needed for all analytic questions @@ -358,6 +359,7 @@ protected final AnalyticQuestionDescription getCurrentQuestion() { * * @return the answered {@link AnalyticQuestion}. */ + @SuppressWarnings({"unchecked", "rawtypes"}) protected final AnalyticQuestion answerCurrentQuestion() throws AnalyticException { // build question @@ -507,6 +509,7 @@ private void setPluginsFromSelectedQuestion() { updateGlobalParameters(); } + @SuppressWarnings({"unchecked", "rawtypes"}) public final void updateSelectablePluginsParameters() { if (categoryListPane.isExpanded()) { LOGGER.log(Level.INFO, "Update selectable plugins parameters in analytic config pane."); @@ -538,6 +541,7 @@ private static void setSuppressedFlag(final boolean newValue) { selectionSuppressed = newValue; } + @SuppressWarnings("rawtypes") public final class SelectableAnalyticPlugin { private final CheckBox checkbox; diff --git a/CoreAnalyticView/src/au/gov/asd/tac/constellation/views/analyticview/AnalyticResultsPane.java b/CoreAnalyticView/src/au/gov/asd/tac/constellation/views/analyticview/AnalyticResultsPane.java index c92f0ad2b2..a036586072 100644 --- a/CoreAnalyticView/src/au/gov/asd/tac/constellation/views/analyticview/AnalyticResultsPane.java +++ b/CoreAnalyticView/src/au/gov/asd/tac/constellation/views/analyticview/AnalyticResultsPane.java @@ -95,6 +95,7 @@ protected final AnalyticResult getResult() { return result; } + @SuppressWarnings("unchecked") protected final void displayResults(final AnalyticQuestion question) { result = question.getResult() == null ? new EmptyResult() : question.getResult(); result.setAnalyticController(analyticController); diff --git a/CoreAttributeEditorView/src/au/gov/asd/tac/constellation/views/attributeeditor/AttributeEditorPanel.java b/CoreAttributeEditorView/src/au/gov/asd/tac/constellation/views/attributeeditor/AttributeEditorPanel.java index 3f30de779c..3d5f856da5 100644 --- a/CoreAttributeEditorView/src/au/gov/asd/tac/constellation/views/attributeeditor/AttributeEditorPanel.java +++ b/CoreAttributeEditorView/src/au/gov/asd/tac/constellation/views/attributeeditor/AttributeEditorPanel.java @@ -859,6 +859,7 @@ private void updateTimeZoneAction(final AttributeData attr) { dialog.showDialog(); } + @SuppressWarnings("unchecked") private void editKeysAction(final GraphElementType elementType) { final List currentKeyAttributes = new ArrayList<>(); final List allAttributes = new ArrayList<>(); @@ -885,6 +886,7 @@ private void editKeysAction(final GraphElementType elementType) { } } + @SuppressWarnings({"unchecked", "rawtypes"}) private EventHandler getEditValueHandler(final AttributeData attributeData, final AttributeValueEditorFactory editorFactory, final Object[] values) { return e -> { final Object value = values.length == 1 ? values[0] : null; diff --git a/CoreDataAccessView/src/au/gov/asd/tac/constellation/views/dataaccess/plugins/experimental/TestParametersPlugin.java b/CoreDataAccessView/src/au/gov/asd/tac/constellation/views/dataaccess/plugins/experimental/TestParametersPlugin.java index f8c22608d0..751f61a08b 100644 --- a/CoreDataAccessView/src/au/gov/asd/tac/constellation/views/dataaccess/plugins/experimental/TestParametersPlugin.java +++ b/CoreDataAccessView/src/au/gov/asd/tac/constellation/views/dataaccess/plugins/experimental/TestParametersPlugin.java @@ -162,6 +162,7 @@ public String getDescription() { return "Test the various input UIs"; } + @SuppressWarnings("unchecked") @Override public PluginParameters createParameters() { final PluginParameters params = new PluginParameters(); diff --git a/CoreDependencies/nbproject/project.properties b/CoreDependencies/nbproject/project.properties index f3283e71dd..88c1f7326e 100644 --- a/CoreDependencies/nbproject/project.properties +++ b/CoreDependencies/nbproject/project.properties @@ -1,34 +1,66 @@ +file.reference.activation-1.1-jar.jar=release/modules/ext/activation-1.1-jar.jar file.reference.activation-1.1.jar=release/modules/ext/activation-1.1.jar +file.reference.assertj-core-3.16.1-jar.jar=release/modules/ext/assertj-core-3.16.1-jar.jar +file.reference.bigint-0.7.1-jar.jar=release/modules/ext/bigint-0.7.1-jar.jar file.reference.bigint-0.7.1.jar=release/modules/ext/bigint-0.7.1.jar +file.reference.checker-qual-2.10.0-jar.jar=release/modules/ext/checker-qual-2.10.0-jar.jar file.reference.checker-qual-2.10.0.jar=release/modules/ext/checker-qual-2.10.0.jar +file.reference.commons-codec-1.13-jar.jar=release/modules/ext/commons-codec-1.13-jar.jar file.reference.commons-codec-1.13.jar=release/modules/ext/commons-codec-1.13.jar +file.reference.commons-collections-3.2.2-jar.jar=release/modules/ext/commons-collections-3.2.2-jar.jar file.reference.commons-collections-3.2.2.jar=release/modules/ext/commons-collections-3.2.2.jar +file.reference.commons-collections4-4.4-jar.jar=release/modules/ext/commons-collections4-4.4-jar.jar file.reference.commons-collections4-4.4.jar=release/modules/ext/commons-collections4-4.4.jar +file.reference.commons-compress-1.19-jar.jar=release/modules/ext/commons-compress-1.19-jar.jar file.reference.commons-compress-1.19.jar=release/modules/ext/commons-compress-1.19.jar +file.reference.commons-csv-1.8-jar.jar=release/modules/ext/commons-csv-1.8-jar.jar file.reference.commons-csv-1.8.jar=release/modules/ext/commons-csv-1.8.jar +file.reference.commons-dbcp-1.4-jar.jar=release/modules/ext/commons-dbcp-1.4-jar.jar file.reference.commons-dbcp-1.4.jar=release/modules/ext/commons-dbcp-1.4.jar +file.reference.commons-io-2.6-jar.jar=release/modules/ext/commons-io-2.6-jar.jar file.reference.commons-io-2.6.jar=release/modules/ext/commons-io-2.6.jar +file.reference.commons-jxpath-1.3-jar.jar=release/modules/ext/commons-jxpath-1.3-jar.jar file.reference.commons-jxpath-1.3.jar=release/modules/ext/commons-jxpath-1.3.jar +file.reference.commons-lang3-3.10-jar.jar=release/modules/ext/commons-lang3-3.10-jar.jar file.reference.commons-lang3-3.10.jar=release/modules/ext/commons-lang3-3.10.jar +file.reference.commons-math3-3.6.1-jar.jar=release/modules/ext/commons-math3-3.6.1-jar.jar file.reference.commons-math3-3.6.1.jar=release/modules/ext/commons-math3-3.6.1.jar +file.reference.commons-pool-1.5.4-jar.jar=release/modules/ext/commons-pool-1.5.4-jar.jar file.reference.commons-pool-1.5.4.jar=release/modules/ext/commons-pool-1.5.4.jar +file.reference.commons-text-1.8-jar.jar=release/modules/ext/commons-text-1.8-jar.jar file.reference.commons-text-1.8.jar=release/modules/ext/commons-text-1.8.jar +file.reference.controlsfx-11.0.1-jar.jar=release/modules/ext/controlsfx-11.0.1-jar.jar file.reference.controlsfx-11.0.1.jar=release/modules/ext/controlsfx-11.0.1.jar file.reference.core.jar=release/modules/ext/core.jar +file.reference.curvesapi-1.06-jar.jar=release/modules/ext/curvesapi-1.06-jar.jar file.reference.curvesapi-1.06.jar=release/modules/ext/curvesapi-1.06.jar +file.reference.ejml-all-0.39-jar.jar=release/modules/ext/ejml-all-0.39-jar.jar file.reference.ejml-all-0.39.jar=release/modules/ext/ejml-all-0.39.jar +file.reference.ejml-cdense-0.39-jar.jar=release/modules/ext/ejml-cdense-0.39-jar.jar file.reference.ejml-cdense-0.39.jar=release/modules/ext/ejml-cdense-0.39.jar +file.reference.ejml-core-0.39-jar.jar=release/modules/ext/ejml-core-0.39-jar.jar file.reference.ejml-core-0.39.jar=release/modules/ext/ejml-core-0.39.jar +file.reference.ejml-ddense-0.39-jar.jar=release/modules/ext/ejml-ddense-0.39-jar.jar file.reference.ejml-ddense-0.39.jar=release/modules/ext/ejml-ddense-0.39.jar +file.reference.ejml-dsparse-0.39-jar.jar=release/modules/ext/ejml-dsparse-0.39-jar.jar file.reference.ejml-dsparse-0.39.jar=release/modules/ext/ejml-dsparse-0.39.jar +file.reference.ejml-fdense-0.39-jar.jar=release/modules/ext/ejml-fdense-0.39-jar.jar file.reference.ejml-fdense-0.39.jar=release/modules/ext/ejml-fdense-0.39.jar +file.reference.ejml-fsparse-0.39-jar.jar=release/modules/ext/ejml-fsparse-0.39-jar.jar file.reference.ejml-fsparse-0.39.jar=release/modules/ext/ejml-fsparse-0.39.jar +file.reference.ejml-simple-0.39-jar.jar=release/modules/ext/ejml-simple-0.39-jar.jar file.reference.ejml-simple-0.39.jar=release/modules/ext/ejml-simple-0.39.jar +file.reference.ejml-zdense-0.39-jar.jar=release/modules/ext/ejml-zdense-0.39-jar.jar file.reference.ejml-zdense-0.39.jar=release/modules/ext/ejml-zdense-0.39.jar +file.reference.error_prone_annotations-2.3.4-jar.jar=release/modules/ext/error_prone_annotations-2.3.4-jar.jar file.reference.error_prone_annotations-2.3.4.jar=release/modules/ext/error_prone_annotations-2.3.4.jar +file.reference.failureaccess-1.0.1-bundle.jar=release/modules/ext/failureaccess-1.0.1-bundle.jar file.reference.failureaccess-1.0.1.jar=release/modules/ext/failureaccess-1.0.1.jar +file.reference.FastInfoset-1.2.15-jar.jar=release/modules/ext/FastInfoset-1.2.15-jar.jar file.reference.FastInfoset-1.2.15.jar=release/modules/ext/FastInfoset-1.2.15.jar +file.reference.filters-2.0.235-jar.jar=release/modules/ext/filters-2.0.235-jar.jar file.reference.filters-2.0.235.jar=release/modules/ext/filters-2.0.235.jar +file.reference.GeographicLib-Java-1.49-jar.jar=release/modules/ext/GeographicLib-Java-1.49-jar.jar file.reference.GeographicLib-Java-1.49.jar=release/modules/ext/GeographicLib-Java-1.49.jar file.reference.gluegen-rt-2.3.2-natives-solaris-amd64.jar=release/modules/ext/gluegen-rt-2.3.2-natives-solaris-amd64.jar file.reference.gluegen-rt-natives-linux-amd64.jar=release/modules/ext/gluegen-rt-natives-linux-amd64.jar @@ -37,55 +69,106 @@ file.reference.gluegen-rt-natives-macosx-universal.jar=release/modules/ext/glueg file.reference.gluegen-rt-natives-windows-amd64.jar=release/modules/ext/gluegen-rt-natives-windows-amd64.jar file.reference.gluegen-rt-natives-windows-i586.jar=release/modules/ext/gluegen-rt-natives-windows-i586.jar file.reference.gluegen-rt.jar=release/modules/ext/gluegen-rt.jar +file.reference.gt-coverage-22.3-jar.jar=release/modules/ext/gt-coverage-22.3-jar.jar file.reference.gt-coverage-22.3.jar=release/modules/ext/gt-coverage-22.3.jar +file.reference.gt-cql-22.3-jar.jar=release/modules/ext/gt-cql-22.3-jar.jar file.reference.gt-cql-22.3.jar=release/modules/ext/gt-cql-22.3.jar +file.reference.gt-epsg-hsql-22.3-jar.jar=release/modules/ext/gt-epsg-hsql-22.3-jar.jar file.reference.gt-epsg-hsql-22.3.jar=release/modules/ext/gt-epsg-hsql-22.3.jar +file.reference.gt-geojson-22.3-jar.jar=release/modules/ext/gt-geojson-22.3-jar.jar file.reference.gt-geojson-22.3.jar=release/modules/ext/gt-geojson-22.3.jar +file.reference.gt-geopkg-22.3-jar.jar=release/modules/ext/gt-geopkg-22.3-jar.jar file.reference.gt-geopkg-22.3.jar=release/modules/ext/gt-geopkg-22.3.jar +file.reference.gt-jdbc-22.3-jar.jar=release/modules/ext/gt-jdbc-22.3-jar.jar file.reference.gt-jdbc-22.3.jar=release/modules/ext/gt-jdbc-22.3.jar +file.reference.gt-main-22.3-jar.jar=release/modules/ext/gt-main-22.3-jar.jar file.reference.gt-main-22.3.jar=release/modules/ext/gt-main-22.3.jar +file.reference.gt-metadata-22.3-jar.jar=release/modules/ext/gt-metadata-22.3-jar.jar file.reference.gt-metadata-22.3.jar=release/modules/ext/gt-metadata-22.3.jar +file.reference.gt-opengis-22.3-jar.jar=release/modules/ext/gt-opengis-22.3-jar.jar file.reference.gt-opengis-22.3.jar=release/modules/ext/gt-opengis-22.3.jar +file.reference.gt-referencing-22.3-jar.jar=release/modules/ext/gt-referencing-22.3-jar.jar file.reference.gt-referencing-22.3.jar=release/modules/ext/gt-referencing-22.3.jar +file.reference.gt-shapefile-22.3-jar.jar=release/modules/ext/gt-shapefile-22.3-jar.jar file.reference.gt-shapefile-22.3.jar=release/modules/ext/gt-shapefile-22.3.jar +file.reference.gt-xml-22.3-jar.jar=release/modules/ext/gt-xml-22.3-jar.jar file.reference.gt-xml-22.3.jar=release/modules/ext/gt-xml-22.3.jar +file.reference.gt-xsd-core-22.3-jar.jar=release/modules/ext/gt-xsd-core-22.3-jar.jar file.reference.gt-xsd-core-22.3.jar=release/modules/ext/gt-xsd-core-22.3.jar +file.reference.gt-xsd-fes-22.3-jar.jar=release/modules/ext/gt-xsd-fes-22.3-jar.jar file.reference.gt-xsd-fes-22.3.jar=release/modules/ext/gt-xsd-fes-22.3.jar +file.reference.gt-xsd-filter-22.3-jar.jar=release/modules/ext/gt-xsd-filter-22.3-jar.jar file.reference.gt-xsd-filter-22.3.jar=release/modules/ext/gt-xsd-filter-22.3.jar +file.reference.gt-xsd-gml2-22.3-jar.jar=release/modules/ext/gt-xsd-gml2-22.3-jar.jar file.reference.gt-xsd-gml2-22.3.jar=release/modules/ext/gt-xsd-gml2-22.3.jar +file.reference.gt-xsd-gml3-22.3-jar.jar=release/modules/ext/gt-xsd-gml3-22.3-jar.jar file.reference.gt-xsd-gml3-22.3.jar=release/modules/ext/gt-xsd-gml3-22.3.jar +file.reference.gt-xsd-kml-22.3-jar.jar=release/modules/ext/gt-xsd-kml-22.3-jar.jar file.reference.gt-xsd-kml-22.3.jar=release/modules/ext/gt-xsd-kml-22.3.jar +file.reference.gt-xsd-ows-22.3-jar.jar=release/modules/ext/gt-xsd-ows-22.3-jar.jar file.reference.gt-xsd-ows-22.3.jar=release/modules/ext/gt-xsd-ows-22.3.jar +file.reference.guava-28.2-jre-bundle.jar=release/modules/ext/guava-28.2-jre-bundle.jar file.reference.guava-28.2-jre.jar=release/modules/ext/guava-28.2-jre.jar +file.reference.hsqldb-2.4.1-jar.jar=release/modules/ext/hsqldb-2.4.1-jar.jar file.reference.hsqldb-2.4.1.jar=release/modules/ext/hsqldb-2.4.1.jar +file.reference.imageio-ext-geocore-1.3.2-jar.jar=release/modules/ext/imageio-ext-geocore-1.3.2-jar.jar file.reference.imageio-ext-geocore-1.3.2.jar=release/modules/ext/imageio-ext-geocore-1.3.2.jar +file.reference.imageio-ext-streams-1.3.2-jar.jar=release/modules/ext/imageio-ext-streams-1.3.2-jar.jar file.reference.imageio-ext-streams-1.3.2.jar=release/modules/ext/imageio-ext-streams-1.3.2.jar +file.reference.imageio-ext-tiff-1.3.2-jar.jar=release/modules/ext/imageio-ext-tiff-1.3.2-jar.jar file.reference.imageio-ext-tiff-1.3.2.jar=release/modules/ext/imageio-ext-tiff-1.3.2.jar +file.reference.imageio-ext-utilities-1.3.2-jar.jar=release/modules/ext/imageio-ext-utilities-1.3.2-jar.jar file.reference.imageio-ext-utilities-1.3.2.jar=release/modules/ext/imageio-ext-utilities-1.3.2.jar +file.reference.istack-commons-runtime-3.0.7-jar.jar=release/modules/ext/istack-commons-runtime-3.0.7-jar.jar file.reference.istack-commons-runtime-3.0.7.jar=release/modules/ext/istack-commons-runtime-3.0.7.jar +file.reference.j2objc-annotations-1.3-jar.jar=release/modules/ext/j2objc-annotations-1.3-jar.jar file.reference.j2objc-annotations-1.3.jar=release/modules/ext/j2objc-annotations-1.3.jar +file.reference.jackson-annotations-2.10.3-bundle.jar=release/modules/ext/jackson-annotations-2.10.3-bundle.jar file.reference.jackson-annotations-2.10.3.jar=release/modules/ext/jackson-annotations-2.10.3.jar +file.reference.jackson-core-2.10.3-bundle.jar=release/modules/ext/jackson-core-2.10.3-bundle.jar file.reference.jackson-core-2.10.3.jar=release/modules/ext/jackson-core-2.10.3.jar +file.reference.jackson-databind-2.10.3-bundle.jar=release/modules/ext/jackson-databind-2.10.3-bundle.jar file.reference.jackson-databind-2.10.3.jar=release/modules/ext/jackson-databind-2.10.3.jar +file.reference.javaee-api-7.0-jar.jar=release/modules/ext/javaee-api-7.0-jar.jar file.reference.javaee-api-7.0.jar=release/modules/ext/javaee-api-7.0.jar +file.reference.javafx-base-11.0.2-jar.jar=release/modules/ext/javafx-base-11.0.2-jar.jar file.reference.javafx-base-11.0.2.jar=release/modules/ext/javafx-base-11.0.2.jar +file.reference.javafx-controls-11.0.2-jar.jar=release/modules/ext/javafx-controls-11.0.2-jar.jar file.reference.javafx-controls-11.0.2.jar=release/modules/ext/javafx-controls-11.0.2.jar +file.reference.javafx-graphics-11.0.2-jar.jar=release/modules/ext/javafx-graphics-11.0.2-jar.jar file.reference.javafx-graphics-11.0.2.jar=release/modules/ext/javafx-graphics-11.0.2.jar +file.reference.javafx-media-11.0.2-jar.jar=release/modules/ext/javafx-media-11.0.2-jar.jar file.reference.javafx-media-11.0.2.jar=release/modules/ext/javafx-media-11.0.2.jar +file.reference.javafx-swing-11.0.2-jar.jar=release/modules/ext/javafx-swing-11.0.2-jar.jar file.reference.javafx-swing-11.0.2.jar=release/modules/ext/javafx-swing-11.0.2.jar +file.reference.javafx-web-11.0.2-jar.jar=release/modules/ext/javafx-web-11.0.2-jar.jar file.reference.javafx-web-11.0.2.jar=release/modules/ext/javafx-web-11.0.2.jar +file.reference.javahelp-2.0.05-jar.jar=release/modules/ext/javahelp-2.0.05-jar.jar +file.reference.javax.activation-api-1.2.0-jar.jar=release/modules/ext/javax.activation-api-1.2.0-jar.jar file.reference.javax.activation-api-1.2.0.jar=release/modules/ext/javax.activation-api-1.2.0.jar +file.reference.javax.mail-1.5.0-jar.jar=release/modules/ext/javax.mail-1.5.0-jar.jar file.reference.javax.mail-1.5.0.jar=release/modules/ext/javax.mail-1.5.0.jar +file.reference.javax.servlet-api-3.1.0-jar.jar=release/modules/ext/javax.servlet-api-3.1.0-jar.jar file.reference.javax.servlet-api-3.1.0.jar=release/modules/ext/javax.servlet-api-3.1.0.jar +file.reference.jaxb-api-2.4.0-b180830.0359-jar.jar=release/modules/ext/jaxb-api-2.4.0-b180830.0359-jar.jar file.reference.jaxb-api-2.4.0-b180830.0359.jar=release/modules/ext/jaxb-api-2.4.0-b180830.0359.jar +file.reference.jaxb-runtime-2.4.0-b180830.0438-jar.jar=release/modules/ext/jaxb-runtime-2.4.0-b180830.0438-jar.jar file.reference.jaxb-runtime-2.4.0-b180830.0438.jar=release/modules/ext/jaxb-runtime-2.4.0-b180830.0438.jar +file.reference.jdom2-2.0.6-jar.jar=release/modules/ext/jdom2-2.0.6-jar.jar file.reference.jdom2-2.0.6.jar=release/modules/ext/jdom2-2.0.6.jar +file.reference.jetty-http-9.4.27.v20200227-jar.jar=release/modules/ext/jetty-http-9.4.27.v20200227-jar.jar file.reference.jetty-http-9.4.27.v20200227.jar=release/modules/ext/jetty-http-9.4.27.v20200227.jar +file.reference.jetty-io-9.4.27.v20200227-jar.jar=release/modules/ext/jetty-io-9.4.27.v20200227-jar.jar file.reference.jetty-io-9.4.27.v20200227.jar=release/modules/ext/jetty-io-9.4.27.v20200227.jar +file.reference.jetty-security-9.4.27.v20200227-jar.jar=release/modules/ext/jetty-security-9.4.27.v20200227-jar.jar file.reference.jetty-security-9.4.27.v20200227.jar=release/modules/ext/jetty-security-9.4.27.v20200227.jar +file.reference.jetty-server-9.4.27.v20200227-jar.jar=release/modules/ext/jetty-server-9.4.27.v20200227-jar.jar file.reference.jetty-server-9.4.27.v20200227.jar=release/modules/ext/jetty-server-9.4.27.v20200227.jar +file.reference.jetty-servlet-9.4.27.v20200227-jar.jar=release/modules/ext/jetty-servlet-9.4.27.v20200227-jar.jar file.reference.jetty-servlet-9.4.27.v20200227.jar=release/modules/ext/jetty-servlet-9.4.27.v20200227.jar +file.reference.jetty-util-9.4.27.v20200227-jar.jar=release/modules/ext/jetty-util-9.4.27.v20200227-jar.jar file.reference.jetty-util-9.4.27.v20200227.jar=release/modules/ext/jetty-util-9.4.27.v20200227.jar +file.reference.jgridshift-core-1.2-jar.jar=release/modules/ext/jgridshift-core-1.2-jar.jar file.reference.jgridshift-core-1.2.jar=release/modules/ext/jgridshift-core-1.2.jar file.reference.jocl-natives-linux-amd64.jar=release/modules/ext/jocl-natives-linux-amd64.jar file.reference.jocl-natives-linux-i586.jar=release/modules/ext/jocl-natives-linux-i586.jar @@ -99,79 +182,216 @@ file.reference.jogl-all-natives-macosx-universal.jar=release/modules/ext/jogl-al file.reference.jogl-all-natives-windows-amd64.jar=release/modules/ext/jogl-all-natives-windows-amd64.jar file.reference.jogl-all-natives-windows-i586.jar=release/modules/ext/jogl-all-natives-windows-i586.jar file.reference.jogl-all.jar=release/modules/ext/jogl-all.jar +file.reference.json-20190722-bundle.jar=release/modules/ext/json-20190722-bundle.jar file.reference.json-20190722.jar=release/modules/ext/json-20190722.jar +file.reference.json-simple-1.1-jar.jar=release/modules/ext/json-simple-1.1-jar.jar file.reference.json-simple-1.1.jar=release/modules/ext/json-simple-1.1.jar +file.reference.jsr305-3.0.2-jar.jar=release/modules/ext/jsr305-3.0.2-jar.jar file.reference.jsr305-3.0.2.jar=release/modules/ext/jsr305-3.0.2.jar file.reference.jt-affine-1.1.12.jar=release/modules/ext/jt-affine-1.1.12.jar +file.reference.jt-affine-1.1.14-jar.jar=release/modules/ext/jt-affine-1.1.14-jar.jar +file.reference.jt-affine-1.1.14.jar=release/modules/ext/jt-affine-1.1.14.jar file.reference.jt-algebra-1.1.12.jar=release/modules/ext/jt-algebra-1.1.12.jar +file.reference.jt-algebra-1.1.14-jar.jar=release/modules/ext/jt-algebra-1.1.14-jar.jar +file.reference.jt-algebra-1.1.14.jar=release/modules/ext/jt-algebra-1.1.14.jar file.reference.jt-bandcombine-1.1.12.jar=release/modules/ext/jt-bandcombine-1.1.12.jar +file.reference.jt-bandcombine-1.1.14-jar.jar=release/modules/ext/jt-bandcombine-1.1.14-jar.jar +file.reference.jt-bandcombine-1.1.14.jar=release/modules/ext/jt-bandcombine-1.1.14.jar file.reference.jt-bandmerge-1.1.12.jar=release/modules/ext/jt-bandmerge-1.1.12.jar +file.reference.jt-bandmerge-1.1.14-jar.jar=release/modules/ext/jt-bandmerge-1.1.14-jar.jar +file.reference.jt-bandmerge-1.1.14.jar=release/modules/ext/jt-bandmerge-1.1.14.jar file.reference.jt-bandselect-1.1.12.jar=release/modules/ext/jt-bandselect-1.1.12.jar +file.reference.jt-bandselect-1.1.14-jar.jar=release/modules/ext/jt-bandselect-1.1.14-jar.jar +file.reference.jt-bandselect-1.1.14.jar=release/modules/ext/jt-bandselect-1.1.14.jar file.reference.jt-binarize-1.1.12.jar=release/modules/ext/jt-binarize-1.1.12.jar +file.reference.jt-binarize-1.1.14-jar.jar=release/modules/ext/jt-binarize-1.1.14-jar.jar +file.reference.jt-binarize-1.1.14.jar=release/modules/ext/jt-binarize-1.1.14.jar file.reference.jt-border-1.1.12.jar=release/modules/ext/jt-border-1.1.12.jar +file.reference.jt-border-1.1.14-jar.jar=release/modules/ext/jt-border-1.1.14-jar.jar +file.reference.jt-border-1.1.14.jar=release/modules/ext/jt-border-1.1.14.jar file.reference.jt-buffer-1.1.12.jar=release/modules/ext/jt-buffer-1.1.12.jar +file.reference.jt-buffer-1.1.14-jar.jar=release/modules/ext/jt-buffer-1.1.14-jar.jar +file.reference.jt-buffer-1.1.14.jar=release/modules/ext/jt-buffer-1.1.14.jar file.reference.jt-classifier-1.1.12.jar=release/modules/ext/jt-classifier-1.1.12.jar +file.reference.jt-classifier-1.1.14-jar.jar=release/modules/ext/jt-classifier-1.1.14-jar.jar +file.reference.jt-classifier-1.1.14.jar=release/modules/ext/jt-classifier-1.1.14.jar file.reference.jt-colorconvert-1.1.12.jar=release/modules/ext/jt-colorconvert-1.1.12.jar +file.reference.jt-colorconvert-1.1.14-jar.jar=release/modules/ext/jt-colorconvert-1.1.14-jar.jar +file.reference.jt-colorconvert-1.1.14.jar=release/modules/ext/jt-colorconvert-1.1.14.jar file.reference.jt-colorindexer-1.1.12.jar=release/modules/ext/jt-colorindexer-1.1.12.jar +file.reference.jt-colorindexer-1.1.14-jar.jar=release/modules/ext/jt-colorindexer-1.1.14-jar.jar +file.reference.jt-colorindexer-1.1.14.jar=release/modules/ext/jt-colorindexer-1.1.14.jar file.reference.jt-crop-1.1.12.jar=release/modules/ext/jt-crop-1.1.12.jar +file.reference.jt-crop-1.1.14-jar.jar=release/modules/ext/jt-crop-1.1.14-jar.jar +file.reference.jt-crop-1.1.14.jar=release/modules/ext/jt-crop-1.1.14.jar file.reference.jt-errordiffusion-1.1.12.jar=release/modules/ext/jt-errordiffusion-1.1.12.jar +file.reference.jt-errordiffusion-1.1.14-jar.jar=release/modules/ext/jt-errordiffusion-1.1.14-jar.jar +file.reference.jt-errordiffusion-1.1.14.jar=release/modules/ext/jt-errordiffusion-1.1.14.jar file.reference.jt-format-1.1.12.jar=release/modules/ext/jt-format-1.1.12.jar +file.reference.jt-format-1.1.14-jar.jar=release/modules/ext/jt-format-1.1.14-jar.jar +file.reference.jt-format-1.1.14.jar=release/modules/ext/jt-format-1.1.14.jar file.reference.jt-imagefunction-1.1.12.jar=release/modules/ext/jt-imagefunction-1.1.12.jar +file.reference.jt-imagefunction-1.1.14-jar.jar=release/modules/ext/jt-imagefunction-1.1.14-jar.jar +file.reference.jt-imagefunction-1.1.14.jar=release/modules/ext/jt-imagefunction-1.1.14.jar file.reference.jt-iterators-1.1.12.jar=release/modules/ext/jt-iterators-1.1.12.jar +file.reference.jt-iterators-1.1.14-jar.jar=release/modules/ext/jt-iterators-1.1.14-jar.jar +file.reference.jt-iterators-1.1.14.jar=release/modules/ext/jt-iterators-1.1.14.jar file.reference.jt-lookup-1.1.12.jar=release/modules/ext/jt-lookup-1.1.12.jar +file.reference.jt-lookup-1.1.14-jar.jar=release/modules/ext/jt-lookup-1.1.14-jar.jar +file.reference.jt-lookup-1.1.14.jar=release/modules/ext/jt-lookup-1.1.14.jar file.reference.jt-mosaic-1.1.12.jar=release/modules/ext/jt-mosaic-1.1.12.jar +file.reference.jt-mosaic-1.1.14-jar.jar=release/modules/ext/jt-mosaic-1.1.14-jar.jar +file.reference.jt-mosaic-1.1.14.jar=release/modules/ext/jt-mosaic-1.1.14.jar file.reference.jt-nullop-1.1.12.jar=release/modules/ext/jt-nullop-1.1.12.jar +file.reference.jt-nullop-1.1.14-jar.jar=release/modules/ext/jt-nullop-1.1.14-jar.jar +file.reference.jt-nullop-1.1.14.jar=release/modules/ext/jt-nullop-1.1.14.jar file.reference.jt-orderdither-1.1.12.jar=release/modules/ext/jt-orderdither-1.1.12.jar +file.reference.jt-orderdither-1.1.14-jar.jar=release/modules/ext/jt-orderdither-1.1.14-jar.jar +file.reference.jt-orderdither-1.1.14.jar=release/modules/ext/jt-orderdither-1.1.14.jar file.reference.jt-piecewise-1.1.12.jar=release/modules/ext/jt-piecewise-1.1.12.jar +file.reference.jt-piecewise-1.1.14-jar.jar=release/modules/ext/jt-piecewise-1.1.14-jar.jar +file.reference.jt-piecewise-1.1.14.jar=release/modules/ext/jt-piecewise-1.1.14.jar file.reference.jt-rescale-1.1.12.jar=release/modules/ext/jt-rescale-1.1.12.jar +file.reference.jt-rescale-1.1.14-jar.jar=release/modules/ext/jt-rescale-1.1.14-jar.jar +file.reference.jt-rescale-1.1.14.jar=release/modules/ext/jt-rescale-1.1.14.jar file.reference.jt-rlookup-1.1.12.jar=release/modules/ext/jt-rlookup-1.1.12.jar +file.reference.jt-rlookup-1.1.14-jar.jar=release/modules/ext/jt-rlookup-1.1.14-jar.jar +file.reference.jt-rlookup-1.1.14.jar=release/modules/ext/jt-rlookup-1.1.14.jar file.reference.jt-scale-1.1.12.jar=release/modules/ext/jt-scale-1.1.12.jar +file.reference.jt-scale-1.1.14-jar.jar=release/modules/ext/jt-scale-1.1.14-jar.jar +file.reference.jt-scale-1.1.14.jar=release/modules/ext/jt-scale-1.1.14.jar file.reference.jt-scale2-1.1.12.jar=release/modules/ext/jt-scale2-1.1.12.jar +file.reference.jt-scale2-1.1.14-jar.jar=release/modules/ext/jt-scale2-1.1.14-jar.jar +file.reference.jt-scale2-1.1.14.jar=release/modules/ext/jt-scale2-1.1.14.jar file.reference.jt-shadedrelief-1.1.12.jar=release/modules/ext/jt-shadedrelief-1.1.12.jar +file.reference.jt-shadedrelief-1.1.14-jar.jar=release/modules/ext/jt-shadedrelief-1.1.14-jar.jar +file.reference.jt-shadedrelief-1.1.14.jar=release/modules/ext/jt-shadedrelief-1.1.14.jar file.reference.jt-stats-1.1.12.jar=release/modules/ext/jt-stats-1.1.12.jar +file.reference.jt-stats-1.1.14-jar.jar=release/modules/ext/jt-stats-1.1.14-jar.jar +file.reference.jt-stats-1.1.14.jar=release/modules/ext/jt-stats-1.1.14.jar file.reference.jt-translate-1.1.12.jar=release/modules/ext/jt-translate-1.1.12.jar +file.reference.jt-translate-1.1.14-jar.jar=release/modules/ext/jt-translate-1.1.14-jar.jar +file.reference.jt-translate-1.1.14.jar=release/modules/ext/jt-translate-1.1.14.jar file.reference.jt-utilities-1.1.12.jar=release/modules/ext/jt-utilities-1.1.12.jar +file.reference.jt-utilities-1.1.14-jar.jar=release/modules/ext/jt-utilities-1.1.14-jar.jar +file.reference.jt-utilities-1.1.14.jar=release/modules/ext/jt-utilities-1.1.14.jar +file.reference.jt-utils-1.5.0-jar.jar=release/modules/ext/jt-utils-1.5.0-jar.jar file.reference.jt-utils-1.5.0.jar=release/modules/ext/jt-utils-1.5.0.jar file.reference.jt-vectorbin-1.1.12.jar=release/modules/ext/jt-vectorbin-1.1.12.jar +file.reference.jt-vectorbin-1.1.14-jar.jar=release/modules/ext/jt-vectorbin-1.1.14-jar.jar +file.reference.jt-vectorbin-1.1.14.jar=release/modules/ext/jt-vectorbin-1.1.14.jar file.reference.jt-warp-1.1.12.jar=release/modules/ext/jt-warp-1.1.12.jar +file.reference.jt-warp-1.1.14-jar.jar=release/modules/ext/jt-warp-1.1.14-jar.jar +file.reference.jt-warp-1.1.14.jar=release/modules/ext/jt-warp-1.1.14.jar file.reference.jt-zonal-1.1.12.jar=release/modules/ext/jt-zonal-1.1.12.jar +file.reference.jt-zonal-1.1.14-jar.jar=release/modules/ext/jt-zonal-1.1.14-jar.jar +file.reference.jt-zonal-1.1.14.jar=release/modules/ext/jt-zonal-1.1.14.jar +file.reference.jt-zonalstats-1.5.0-jar.jar=release/modules/ext/jt-zonalstats-1.5.0-jar.jar file.reference.jt-zonalstats-1.5.0.jar=release/modules/ext/jt-zonalstats-1.5.0.jar +file.reference.jts-core-1.16.1-bundle.jar=release/modules/ext/jts-core-1.16.1-bundle.jar file.reference.jts-core-1.16.1.jar=release/modules/ext/jts-core-1.16.1.jar +file.reference.jython-standalone-2.7.2-jar.jar=release/modules/ext/jython-standalone-2.7.2-jar.jar file.reference.jython-standalone-2.7.2.jar=release/modules/ext/jython-standalone-2.7.2.jar +file.reference.listenablefuture-9999.0-empty-to-avoid-conflict-with-guava-jar.jar=release/modules/ext/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava-jar.jar file.reference.listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar=release/modules/ext/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar +file.reference.log4j-1.2.17-bundle.jar=release/modules/ext/log4j-1.2.17-bundle.jar file.reference.log4j-1.2.17.jar=release/modules/ext/log4j-1.2.17.jar +file.reference.lwjgl-3.2.3-jar-javadoc.jar=release/modules/ext/lwjgl-3.2.3-jar-javadoc.jar +file.reference.lwjgl-3.2.3-jar-natives-linux.jar=release/modules/ext/lwjgl-3.2.3-jar-natives-linux.jar +file.reference.lwjgl-3.2.3-jar-natives-macos.jar=release/modules/ext/lwjgl-3.2.3-jar-natives-macos.jar +file.reference.lwjgl-3.2.3-jar-natives-windows-x86.jar=release/modules/ext/lwjgl-3.2.3-jar-natives-windows-x86.jar +file.reference.lwjgl-3.2.3-jar-natives-windows.jar=release/modules/ext/lwjgl-3.2.3-jar-natives-windows.jar +file.reference.lwjgl-3.2.3-jar-sources.jar=release/modules/ext/lwjgl-3.2.3-jar-sources.jar +file.reference.lwjgl-3.2.3-jar.jar=release/modules/ext/lwjgl-3.2.3-jar.jar +file.reference.lwjgl-jawt-3.2.3-jar.jar=release/modules/ext/lwjgl-jawt-3.2.3-jar.jar +file.reference.lwjgl-shaderc-3.2.3-jar-javadoc.jar=release/modules/ext/lwjgl-shaderc-3.2.3-jar-javadoc.jar +file.reference.lwjgl-shaderc-3.2.3-jar-natives-windows.jar=release/modules/ext/lwjgl-shaderc-3.2.3-jar-natives-windows.jar +file.reference.lwjgl-shaderc-3.2.3-jar-sources.jar=release/modules/ext/lwjgl-shaderc-3.2.3-jar-sources.jar +file.reference.lwjgl-shaderc-3.2.3-jar.jar=release/modules/ext/lwjgl-shaderc-3.2.3-jar.jar +file.reference.lwjgl-vulkan-3.2.3-jar-javadoc.jar=release/modules/ext/lwjgl-vulkan-3.2.3-jar-javadoc.jar +file.reference.lwjgl-vulkan-3.2.3-jar-natives-macos.jar=release/modules/ext/lwjgl-vulkan-3.2.3-jar-natives-macos.jar +file.reference.lwjgl-vulkan-3.2.3-jar-sources.jar=release/modules/ext/lwjgl-vulkan-3.2.3-jar-sources.jar +file.reference.lwjgl-vulkan-3.2.3-jar.jar=release/modules/ext/lwjgl-vulkan-3.2.3-jar.jar file.reference.nativewindow-natives-windows-i586.jar=release/modules/ext/nativewindow-natives-windows-i586.jar file.reference.nativewindow.jar=release/modules/ext/nativewindow.jar +file.reference.net.opengis.fes-22.3-jar.jar=release/modules/ext/net.opengis.fes-22.3-jar.jar file.reference.net.opengis.fes-22.3.jar=release/modules/ext/net.opengis.fes-22.3.jar +file.reference.net.opengis.ows-22.3-jar.jar=release/modules/ext/net.opengis.ows-22.3-jar.jar file.reference.net.opengis.ows-22.3.jar=release/modules/ext/net.opengis.ows-22.3.jar file.reference.newt-natives-windows-i586.jar=release/modules/ext/newt-natives-windows-i586.jar file.reference.newt.jar=release/modules/ext/newt.jar +file.reference.org.eclipse.emf.common-2.15.0-jar.jar=release/modules/ext/org.eclipse.emf.common-2.15.0-jar.jar file.reference.org.eclipse.emf.common-2.15.0.jar=release/modules/ext/org.eclipse.emf.common-2.15.0.jar +file.reference.org.eclipse.emf.ecore-2.15.0-jar.jar=release/modules/ext/org.eclipse.emf.ecore-2.15.0-jar.jar file.reference.org.eclipse.emf.ecore-2.15.0.jar=release/modules/ext/org.eclipse.emf.ecore-2.15.0.jar +file.reference.org.eclipse.emf.ecore.xmi-2.15.0-jar.jar=release/modules/ext/org.eclipse.emf.ecore.xmi-2.15.0-jar.jar file.reference.org.eclipse.emf.ecore.xmi-2.15.0.jar=release/modules/ext/org.eclipse.emf.ecore.xmi-2.15.0.jar +file.reference.org.eclipse.xsd-2.12.0-jar.jar=release/modules/ext/org.eclipse.xsd-2.12.0-jar.jar file.reference.org.eclipse.xsd-2.12.0.jar=release/modules/ext/org.eclipse.xsd-2.12.0.jar +file.reference.org.w3.xlink-22.3-jar.jar=release/modules/ext/org.w3.xlink-22.3-jar.jar file.reference.org.w3.xlink-22.3.jar=release/modules/ext/org.w3.xlink-22.3.jar +file.reference.picocontainer-1.2-jar.jar=release/modules/ext/picocontainer-1.2-jar.jar file.reference.picocontainer-1.2.jar=release/modules/ext/picocontainer-1.2.jar +file.reference.poi-4.1.2-jar.jar=release/modules/ext/poi-4.1.2-jar.jar file.reference.poi-4.1.2.jar=release/modules/ext/poi-4.1.2.jar +file.reference.poi-ooxml-4.1.2-jar.jar=release/modules/ext/poi-ooxml-4.1.2-jar.jar file.reference.poi-ooxml-4.1.2.jar=release/modules/ext/poi-ooxml-4.1.2.jar +file.reference.poi-ooxml-schemas-4.1.2-jar.jar=release/modules/ext/poi-ooxml-schemas-4.1.2-jar.jar file.reference.poi-ooxml-schemas-4.1.2.jar=release/modules/ext/poi-ooxml-schemas-4.1.2.jar +file.reference.rsyntaxtextarea-3.1.1-jar.jar=release/modules/ext/rsyntaxtextarea-3.1.1-jar.jar file.reference.rsyntaxtextarea-3.1.1.jar=release/modules/ext/rsyntaxtextarea-3.1.1.jar +file.reference.si-quantity-0.7.1-bundle.jar=release/modules/ext/si-quantity-0.7.1-bundle.jar file.reference.si-quantity-0.7.1.jar=release/modules/ext/si-quantity-0.7.1.jar +file.reference.si-units-java8-0.7.1-jar.jar=release/modules/ext/si-units-java8-0.7.1-jar.jar file.reference.si-units-java8-0.7.1.jar=release/modules/ext/si-units-java8-0.7.1.jar +file.reference.SparseBitSet-1.2-jar.jar=release/modules/ext/SparseBitSet-1.2-jar.jar file.reference.SparseBitSet-1.2.jar=release/modules/ext/SparseBitSet-1.2.jar +file.reference.sqlite-jdbc-3.27.2.1-jar.jar=release/modules/ext/sqlite-jdbc-3.27.2.1-jar.jar file.reference.sqlite-jdbc-3.27.2.1.jar=release/modules/ext/sqlite-jdbc-3.27.2.1.jar +file.reference.stax-ex-1.8-jar.jar=release/modules/ext/stax-ex-1.8-jar.jar file.reference.stax-ex-1.8.jar=release/modules/ext/stax-ex-1.8.jar +file.reference.swing-worker-1.1-jar.jar=release/modules/ext/swing-worker-1.1-jar.jar file.reference.swing-worker-1.1.jar=release/modules/ext/swing-worker-1.1.jar +file.reference.swingx-1.6.1-jar.jar=release/modules/ext/swingx-1.6.1-jar.jar file.reference.swingx-1.6.1.jar=release/modules/ext/swingx-1.6.1.jar +file.reference.systems-common-java8-0.7.2-bundle.jar=release/modules/ext/systems-common-java8-0.7.2-bundle.jar file.reference.systems-common-java8-0.7.2.jar=release/modules/ext/systems-common-java8-0.7.2.jar +file.reference.txw2-2.4.0-b180830.0438-jar.jar=release/modules/ext/txw2-2.4.0-b180830.0438-jar.jar file.reference.txw2-2.4.0-b180830.0438.jar=release/modules/ext/txw2-2.4.0-b180830.0438.jar file.reference.unfolding-master-20171010.jar=release/modules/ext/unfolding-master-20171010.jar +file.reference.unit-api-1.0-bundle.jar=release/modules/ext/unit-api-1.0-bundle.jar file.reference.unit-api-1.0.jar=release/modules/ext/unit-api-1.0.jar +file.reference.uom-lib-common-1.0.2-bundle.jar=release/modules/ext/uom-lib-common-1.0.2-bundle.jar file.reference.uom-lib-common-1.0.2.jar=release/modules/ext/uom-lib-common-1.0.2.jar +file.reference.uom-se-1.0.8-bundle.jar=release/modules/ext/uom-se-1.0.8-bundle.jar file.reference.uom-se-1.0.8.jar=release/modules/ext/uom-se-1.0.8.jar +file.reference.worldwind-2.0.0-jar.jar=release/modules/ext/worldwind-2.0.0-jar.jar file.reference.worldwind-2.0.0.jar=release/modules/ext/worldwind-2.0.0.jar +file.reference.xml-commons-resolver-1.2-jar.jar=release/modules/ext/xml-commons-resolver-1.2-jar.jar file.reference.xml-commons-resolver-1.2.jar=release/modules/ext/xml-commons-resolver-1.2.jar +file.reference.xmlbeans-3.1.0-jar.jar=release/modules/ext/xmlbeans-3.1.0-jar.jar file.reference.xmlbeans-3.1.0.jar=release/modules/ext/xmlbeans-3.1.0.jar +file.reference.lwjgl-3.2.3.jar=release/modules/ext/lwjgl-3.2.3.jar +file.reference.lwjgl-3.2.3-javadoc.jar=release/modules/ext/lwjgl-3.2.3-javadoc.jar +file.reference.lwjgl-3.2.3-sources.jar=release/modules/ext/lwjgl-3.2.3-sources.jar +file.reference.lwjgl-3.2.3-natives-windows.jar=release/modules/ext/lwjgl-3.2.3-natives-windows.jar +file.reference.lwjgl-3.2.3-natives-windows-x86.jar=release/modules/ext/lwjgl-3.2.3-natives-windows-x86.jar +file.reference.lwjgl-3.2.3-natives-macos.jar=release/modules/ext/lwjgl-3.2.3-natives-macos.jar +file.reference.lwjgl-3.2.3-natives-linux.jar=release/modules/ext/lwjgl-3.2.3-natives-linux.jar +file.reference.lwjgl-jawt-3.2.3.jar=release/modules/ext/lwjgl-jawt-3.2.3.jar +file.reference.lwjgl-vulkan-3.2.3.jar=release/modules/ext/lwjgl-vulkan-3.2.3.jar +file.reference.lwjgl-vulkan-3.2.3-javadoc.jar=release/modules/ext/lwjgl-vulkan-3.2.3-javadoc.jar +file.reference.lwjgl-vulkan-3.2.3-sources.jar=release/modules/ext/lwjgl-vulkan-3.2.3-sources.jar +file.reference.lwjgl-vulkan-3.2.3-natives-macos.jar=release/modules/ext/lwjgl-vulkan-3.2.3-natives-macos.jar +file.reference.lwjgl3-awt-0.1.7.jar=release/modules/ext/lwjgl3-awt-0.1.7.jar +file.reference.lwjgl3-awt-0.1.7-javadoc.jar=release/modules/ext/lwjgl3-awt-0.1.7-javadoc.jar +file.reference.lwjgl3-awt-0.1.7-sources.jar=release/modules/ext/lwjgl3-awt-0.1.7-sources.jar +file.reference.lwjgl-shaderc-3.2.3.jar=release/modules/ext/lwjgl-shaderc-3.2.3.jar +file.reference.lwjgl-shaderc-3.2.3-javadoc.jar=release/modules/ext/lwjgl-shaderc-3.2.3-javadoc.jar +file.reference.lwjgl-shaderc-3.2.3-sources.jar=release/modules/ext/lwjgl-shaderc-3.2.3-sources.jar +file.reference.lwjgl-shaderc-3.2.3-natives-windows.jar=release/modules/ext/lwjgl-shaderc-3.2.3-natives-windows.jar + + javac.source=11 javac.compilerargs=-Xlint -Xlint:-serial license.file=../LICENSE diff --git a/CoreDependencies/nbproject/project.xml b/CoreDependencies/nbproject/project.xml index ae86ac4669..b5b3239316 100644 --- a/CoreDependencies/nbproject/project.xml +++ b/CoreDependencies/nbproject/project.xml @@ -1690,6 +1690,20 @@ org.locationtech.jts.triangulate org.locationtech.jts.triangulate.quadedge org.locationtech.jts.util + org.lwjgl + org.lwjgl.awthacks + org.lwjgl.opengl.awt + org.lwjgl.system + org.lwjgl.system.dyncall + org.lwjgl.system.jawt + org.lwjgl.system.jni + org.lwjgl.system.libc + org.lwjgl.system.linux + org.lwjgl.system.macosx + org.lwjgl.system.windows + org.lwjgl.util.shaderc + org.lwjgl.vulkan + org.lwjgl.vulkan.awt org.opengis.annotation org.opengis.coverage org.opengis.coverage.grid @@ -2637,136 +2651,136 @@ release/modules/ext/jsr305-3.0.2-jar.jar - ext/jt-affine-1.1.12-jar.jar - release/modules/ext/jt-affine-1.1.12-jar.jar + ext/jt-affine-1.1.14-jar.jar + release/modules/ext/jt-affine-1.1.14-jar.jar - ext/jt-algebra-1.1.12-jar.jar - release/modules/ext/jt-algebra-1.1.12-jar.jar + ext/jt-algebra-1.1.14-jar.jar + release/modules/ext/jt-algebra-1.1.14-jar.jar - ext/jt-bandcombine-1.1.12-jar.jar - release/modules/ext/jt-bandcombine-1.1.12-jar.jar + ext/jt-bandcombine-1.1.14-jar.jar + release/modules/ext/jt-bandcombine-1.1.14-jar.jar - ext/jt-bandmerge-1.1.12-jar.jar - release/modules/ext/jt-bandmerge-1.1.12-jar.jar + ext/jt-bandmerge-1.1.14-jar.jar + release/modules/ext/jt-bandmerge-1.1.14-jar.jar - ext/jt-bandselect-1.1.12-jar.jar - release/modules/ext/jt-bandselect-1.1.12-jar.jar + ext/jt-bandselect-1.1.14-jar.jar + release/modules/ext/jt-bandselect-1.1.14-jar.jar - ext/jt-binarize-1.1.12-jar.jar - release/modules/ext/jt-binarize-1.1.12-jar.jar + ext/jt-binarize-1.1.14-jar.jar + release/modules/ext/jt-binarize-1.1.14-jar.jar - ext/jt-border-1.1.12-jar.jar - release/modules/ext/jt-border-1.1.12-jar.jar + ext/jt-border-1.1.14-jar.jar + release/modules/ext/jt-border-1.1.14-jar.jar - ext/jt-buffer-1.1.12-jar.jar - release/modules/ext/jt-buffer-1.1.12-jar.jar + ext/jt-buffer-1.1.14-jar.jar + release/modules/ext/jt-buffer-1.1.14-jar.jar - ext/jt-classifier-1.1.12-jar.jar - release/modules/ext/jt-classifier-1.1.12-jar.jar + ext/jt-classifier-1.1.14-jar.jar + release/modules/ext/jt-classifier-1.1.14-jar.jar - ext/jt-colorconvert-1.1.12-jar.jar - release/modules/ext/jt-colorconvert-1.1.12-jar.jar + ext/jt-colorconvert-1.1.14-jar.jar + release/modules/ext/jt-colorconvert-1.1.14-jar.jar - ext/jt-colorindexer-1.1.12-jar.jar - release/modules/ext/jt-colorindexer-1.1.12-jar.jar + ext/jt-colorindexer-1.1.14-jar.jar + release/modules/ext/jt-colorindexer-1.1.14-jar.jar - ext/jt-crop-1.1.12-jar.jar - release/modules/ext/jt-crop-1.1.12-jar.jar + ext/jt-crop-1.1.14-jar.jar + release/modules/ext/jt-crop-1.1.14-jar.jar - ext/jt-errordiffusion-1.1.12-jar.jar - release/modules/ext/jt-errordiffusion-1.1.12-jar.jar + ext/jt-errordiffusion-1.1.14-jar.jar + release/modules/ext/jt-errordiffusion-1.1.14-jar.jar - ext/jt-format-1.1.12-jar.jar - release/modules/ext/jt-format-1.1.12-jar.jar + ext/jt-format-1.1.14-jar.jar + release/modules/ext/jt-format-1.1.14-jar.jar - ext/jt-imagefunction-1.1.12-jar.jar - release/modules/ext/jt-imagefunction-1.1.12-jar.jar + ext/jt-imagefunction-1.1.14-jar.jar + release/modules/ext/jt-imagefunction-1.1.14-jar.jar - ext/jt-iterators-1.1.12-jar.jar - release/modules/ext/jt-iterators-1.1.12-jar.jar + ext/jt-iterators-1.1.14-jar.jar + release/modules/ext/jt-iterators-1.1.14-jar.jar - ext/jt-lookup-1.1.12-jar.jar - release/modules/ext/jt-lookup-1.1.12-jar.jar + ext/jt-lookup-1.1.14-jar.jar + release/modules/ext/jt-lookup-1.1.14-jar.jar - ext/jt-mosaic-1.1.12-jar.jar - release/modules/ext/jt-mosaic-1.1.12-jar.jar + ext/jt-mosaic-1.1.14-jar.jar + release/modules/ext/jt-mosaic-1.1.14-jar.jar - ext/jt-nullop-1.1.12-jar.jar - release/modules/ext/jt-nullop-1.1.12-jar.jar + ext/jt-nullop-1.1.14-jar.jar + release/modules/ext/jt-nullop-1.1.14-jar.jar - ext/jt-orderdither-1.1.12-jar.jar - release/modules/ext/jt-orderdither-1.1.12-jar.jar + ext/jt-orderdither-1.1.14-jar.jar + release/modules/ext/jt-orderdither-1.1.14-jar.jar - ext/jt-piecewise-1.1.12-jar.jar - release/modules/ext/jt-piecewise-1.1.12-jar.jar + ext/jt-piecewise-1.1.14-jar.jar + release/modules/ext/jt-piecewise-1.1.14-jar.jar - ext/jt-rescale-1.1.12-jar.jar - release/modules/ext/jt-rescale-1.1.12-jar.jar + ext/jt-rescale-1.1.14-jar.jar + release/modules/ext/jt-rescale-1.1.14-jar.jar - ext/jt-rlookup-1.1.12-jar.jar - release/modules/ext/jt-rlookup-1.1.12-jar.jar + ext/jt-rlookup-1.1.14-jar.jar + release/modules/ext/jt-rlookup-1.1.14-jar.jar - ext/jt-scale-1.1.12-jar.jar - release/modules/ext/jt-scale-1.1.12-jar.jar + ext/jt-scale-1.1.14-jar.jar + release/modules/ext/jt-scale-1.1.14-jar.jar - ext/jt-scale2-1.1.12-jar.jar - release/modules/ext/jt-scale2-1.1.12-jar.jar + ext/jt-scale2-1.1.14-jar.jar + release/modules/ext/jt-scale2-1.1.14-jar.jar - ext/jt-shadedrelief-1.1.12-jar.jar - release/modules/ext/jt-shadedrelief-1.1.12-jar.jar + ext/jt-shadedrelief-1.1.14-jar.jar + release/modules/ext/jt-shadedrelief-1.1.14-jar.jar - ext/jt-stats-1.1.12-jar.jar - release/modules/ext/jt-stats-1.1.12-jar.jar + ext/jt-stats-1.1.14-jar.jar + release/modules/ext/jt-stats-1.1.14-jar.jar - ext/jt-translate-1.1.12-jar.jar - release/modules/ext/jt-translate-1.1.12-jar.jar + ext/jt-translate-1.1.14-jar.jar + release/modules/ext/jt-translate-1.1.14-jar.jar - ext/jt-utilities-1.1.12-jar.jar - release/modules/ext/jt-utilities-1.1.12-jar.jar + ext/jt-utilities-1.1.14-jar.jar + release/modules/ext/jt-utilities-1.1.14-jar.jar ext/jt-utils-1.5.0-jar.jar release/modules/ext/jt-utils-1.5.0-jar.jar - ext/jt-vectorbin-1.1.12-jar.jar - release/modules/ext/jt-vectorbin-1.1.12-jar.jar + ext/jt-vectorbin-1.1.14-jar.jar + release/modules/ext/jt-vectorbin-1.1.14-jar.jar - ext/jt-warp-1.1.12-jar.jar - release/modules/ext/jt-warp-1.1.12-jar.jar + ext/jt-warp-1.1.14-jar.jar + release/modules/ext/jt-warp-1.1.14-jar.jar - ext/jt-zonal-1.1.12-jar.jar - release/modules/ext/jt-zonal-1.1.12-jar.jar + ext/jt-zonal-1.1.14-jar.jar + release/modules/ext/jt-zonal-1.1.14-jar.jar ext/jt-zonalstats-1.5.0-jar.jar @@ -2788,6 +2802,90 @@ ext/log4j-1.2.17-bundle.jar release/modules/ext/log4j-1.2.17-bundle.jar + + ext/lwjgl-3.2.3-jar-javadoc.jar + release/modules/ext/lwjgl-3.2.3-jar-javadoc.jar + + + ext/lwjgl-3.2.3-jar-natives-linux.jar + release/modules/ext/lwjgl-3.2.3-jar-natives-linux.jar + + + ext/lwjgl-3.2.3-jar-natives-macos.jar + release/modules/ext/lwjgl-3.2.3-jar-natives-macos.jar + + + ext/lwjgl-3.2.3-jar-natives-windows-x86.jar + release/modules/ext/lwjgl-3.2.3-jar-natives-windows-x86.jar + + + ext/lwjgl-3.2.3-jar-natives-windows.jar + release/modules/ext/lwjgl-3.2.3-jar-natives-windows.jar + + + ext/lwjgl-3.2.3-jar-sources.jar + release/modules/ext/lwjgl-3.2.3-jar-sources.jar + + + ext/lwjgl-3.2.3-jar.jar + release/modules/ext/lwjgl-3.2.3-jar.jar + + + ext/lwjgl-jawt-3.2.3-jar.jar + release/modules/ext/lwjgl-jawt-3.2.3-jar.jar + + + ext/lwjgl-shaderc-3.2.3-jar-javadoc.jar + release/modules/ext/lwjgl-shaderc-3.2.3-jar-javadoc.jar + + + ext/lwjgl-shaderc-3.2.3-jar-natives-linux.jar + release/modules/ext/lwjgl-shaderc-3.2.3-jar-natives-linux.jar + + + ext/lwjgl-shaderc-3.2.3-jar-natives-macos.jar + release/modules/ext/lwjgl-shaderc-3.2.3-jar-natives-macos.jar + + + ext/lwjgl-shaderc-3.2.3-jar-natives-windows.jar + release/modules/ext/lwjgl-shaderc-3.2.3-jar-natives-windows.jar + + + ext/lwjgl-shaderc-3.2.3-jar-sources.jar + release/modules/ext/lwjgl-shaderc-3.2.3-jar-sources.jar + + + ext/lwjgl-shaderc-3.2.3-jar.jar + release/modules/ext/lwjgl-shaderc-3.2.3-jar.jar + + + ext/lwjgl-vulkan-3.2.3-jar-javadoc.jar + release/modules/ext/lwjgl-vulkan-3.2.3-jar-javadoc.jar + + + ext/lwjgl-vulkan-3.2.3-jar-natives-macos.jar + release/modules/ext/lwjgl-vulkan-3.2.3-jar-natives-macos.jar + + + ext/lwjgl-vulkan-3.2.3-jar-sources.jar + release/modules/ext/lwjgl-vulkan-3.2.3-jar-sources.jar + + + ext/lwjgl-vulkan-3.2.3-jar.jar + release/modules/ext/lwjgl-vulkan-3.2.3-jar.jar + + + ext/lwjgl3-awt-0.1.7-javadoc.jar + release/modules/ext/lwjgl3-awt-0.1.7-javadoc.jar + + + ext/lwjgl3-awt-0.1.7-sources.jar + release/modules/ext/lwjgl3-awt-0.1.7-sources.jar + + + ext/lwjgl3-awt-0.1.7.jar + release/modules/ext/lwjgl3-awt-0.1.7.jar + ext/nativewindow-natives-windows-i586.jar release/modules/ext/nativewindow-natives-windows-i586.jar diff --git a/CoreDependencies/src/ivy.xml b/CoreDependencies/src/ivy.xml index 0b35c87f18..c0847d44d5 100644 --- a/CoreDependencies/src/ivy.xml +++ b/CoreDependencies/src/ivy.xml @@ -83,6 +83,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CoreFindView/src/au/gov/asd/tac/constellation/views/find/gui/FindCriteriaPanel.java b/CoreFindView/src/au/gov/asd/tac/constellation/views/find/gui/FindCriteriaPanel.java index fba127fee5..bcb6d6e2b7 100644 --- a/CoreFindView/src/au/gov/asd/tac/constellation/views/find/gui/FindCriteriaPanel.java +++ b/CoreFindView/src/au/gov/asd/tac/constellation/views/find/gui/FindCriteriaPanel.java @@ -224,6 +224,7 @@ public FindRule getState() { * @param isNew are these attribute from a new graph? * @param attributes the new attribute to display. */ + @SuppressWarnings({"unchecked", "rawtypes"}) public void updateAttributes(final boolean isNew, final ArrayList attributes) { if (!attributes.isEmpty()) { cmbAttributes.setModel(new AttributeComboBoxModel(attributes)); @@ -253,6 +254,7 @@ public void updateAttributes(final boolean isNew, final ArrayList att * @param isNew true to get new operator list, * false to use existing. */ + @SuppressWarnings({"unchecked", "rawtypes"}) public void updateOperators(final boolean isNew) { if (cmbAttributes.getSelectedItem() instanceof Attribute) { final Attribute attr = localState.getAttribute(); diff --git a/CoreFunctionality/src/au/gov/asd/tac/constellation/functionality/dialog/ItemsDialog.java b/CoreFunctionality/src/au/gov/asd/tac/constellation/functionality/dialog/ItemsDialog.java index f0537c47a3..4bcf81ac83 100644 --- a/CoreFunctionality/src/au/gov/asd/tac/constellation/functionality/dialog/ItemsDialog.java +++ b/CoreFunctionality/src/au/gov/asd/tac/constellation/functionality/dialog/ItemsDialog.java @@ -56,6 +56,7 @@ * * @author sirius */ +@SuppressWarnings("unchecked") public class ItemsDialog extends ConstellationDialog { private TableView> table; diff --git a/CoreGraphFramework/nbproject/project.xml b/CoreGraphFramework/nbproject/project.xml index 85327c1332..4de7c25998 100644 --- a/CoreGraphFramework/nbproject/project.xml +++ b/CoreGraphFramework/nbproject/project.xml @@ -89,6 +89,7 @@ au.gov.asd.tac.constellation.graph.attribute.io au.gov.asd.tac.constellation.graph.attribute.utilities au.gov.asd.tac.constellation.graph.construction + au.gov.asd.tac.constellation.graph.hittest au.gov.asd.tac.constellation.graph.locking au.gov.asd.tac.constellation.graph.manager au.gov.asd.tac.constellation.graph.mergers diff --git a/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/StoreGraph.java b/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/StoreGraph.java index 40d59b04c0..1191e011e3 100644 --- a/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/StoreGraph.java +++ b/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/StoreGraph.java @@ -210,6 +210,7 @@ public StoreGraph(final String id, final Schema schema, int vertexCapacity, int MemoryManager.newObject(StoreGraph.class); } + @SuppressWarnings("deprecation") @Override public void finalize() { MemoryManager.finalizeObject(StoreGraph.class); diff --git a/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/attribute/AbstractObjectAttributeDescription.java b/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/attribute/AbstractObjectAttributeDescription.java index d419caed4c..7da7485c32 100644 --- a/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/attribute/AbstractObjectAttributeDescription.java +++ b/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/attribute/AbstractObjectAttributeDescription.java @@ -101,6 +101,7 @@ public void setCapacity(final int capacity) { } } + @SuppressWarnings("unchecked") @Override public String getString(final int id) { return data[id] != null ? String.valueOf((T) data[id]) : null; @@ -142,6 +143,7 @@ public void clear(final int id) { data[id] = defaultValue; } + @SuppressWarnings("unchecked") @Override public AttributeDescription copy(final GraphReadMethods graph) { final AbstractObjectAttributeDescription attribute; @@ -168,11 +170,13 @@ public boolean equals(final int id1, final int id2) { return data[id1] == null ? data[id2] == null : data[id1].equals(data[id2]); } + @SuppressWarnings("unchecked") @Override public void save(final int id, final ParameterWriteAccess access) { access.setObject((T) data[id]); } + @SuppressWarnings("unchecked") @Override public void restore(final int id, final ParameterReadAccess access) { data[id] = (T) access.getUndoObject(); diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/framework/HitState.java b/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/hittest/HitState.java similarity index 97% rename from CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/framework/HitState.java rename to CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/hittest/HitState.java index 98b18b4441..24a99c2342 100644 --- a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/framework/HitState.java +++ b/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/hittest/HitState.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.graph.interaction.framework; +package au.gov.asd.tac.constellation.graph.hittest; import au.gov.asd.tac.constellation.graph.GraphElementType; diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/HitTestRequest.java b/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/hittest/HitTestRequest.java similarity index 91% rename from CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/HitTestRequest.java rename to CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/hittest/HitTestRequest.java index a31b761958..760695c52d 100644 --- a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/HitTestRequest.java +++ b/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/hittest/HitTestRequest.java @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.graph.interaction.visual.renderables; +package au.gov.asd.tac.constellation.graph.hittest; -import au.gov.asd.tac.constellation.graph.interaction.framework.HitState; import java.util.Queue; import java.util.function.Consumer; diff --git a/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/locking/DualGraph.java b/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/locking/DualGraph.java index 4b6f45b151..a72c226697 100644 --- a/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/locking/DualGraph.java +++ b/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/locking/DualGraph.java @@ -149,6 +149,7 @@ public DualGraph(final Schema schema, final StoreGraph target, final boolean new MemoryManager.newObject(DualGraph.class); } + @SuppressWarnings("deprecation") @Override public void finalize() throws Throwable { try { diff --git a/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/undo/UndoGraphEditOperation.java b/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/undo/UndoGraphEditOperation.java index 12b4f06eed..9169252a97 100644 --- a/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/undo/UndoGraphEditOperation.java +++ b/CoreGraphFramework/src/au/gov/asd/tac/constellation/graph/undo/UndoGraphEditOperation.java @@ -716,14 +716,14 @@ public void updateUndo(final UndoGraphEditState state, final int instruction) { @Override public void execute(final UndoGraphEditState state, final GraphWriteMethods graph) { if (VERBOSE) { - System.out.println("setDoubleValueOperation.execute(" + state.getCurrentAttribute() + ", " + state.getCurrentId() + ", " + Double.longBitsToDouble((long) (Double.doubleToRawLongBits(graph.getDoubleValue(state.getCurrentAttribute(), state.getCurrentId())) ^ state.getCurrentDouble())) + ")"); + System.out.println("setDoubleValueOperation.execute(" + state.getCurrentAttribute() + ", " + state.getCurrentId() + ", " + Double.longBitsToDouble((Double.doubleToRawLongBits(graph.getDoubleValue(state.getCurrentAttribute(), state.getCurrentId())) ^ state.getCurrentDouble())) + ")"); } - graph.setDoubleValue(state.getCurrentAttribute(), state.getCurrentId(), Double.longBitsToDouble((long) (Double.doubleToRawLongBits(graph.getDoubleValue(state.getCurrentAttribute(), state.getCurrentId())) ^ state.getCurrentDouble()))); + graph.setDoubleValue(state.getCurrentAttribute(), state.getCurrentId(), Double.longBitsToDouble((Double.doubleToRawLongBits(graph.getDoubleValue(state.getCurrentAttribute(), state.getCurrentId())) ^ state.getCurrentDouble()))); } @Override public void undo(final UndoGraphEditState state, final GraphWriteMethods graph) { - graph.setDoubleValue(state.getCurrentAttribute(), state.getCurrentId(), Double.longBitsToDouble((long) (Double.doubleToRawLongBits(graph.getDoubleValue(state.getCurrentAttribute(), state.getCurrentId())) ^ state.getCurrentDouble()))); + graph.setDoubleValue(state.getCurrentAttribute(), state.getCurrentId(), Double.longBitsToDouble((Double.doubleToRawLongBits(graph.getDoubleValue(state.getCurrentAttribute(), state.getCurrentId())) ^ state.getCurrentDouble()))); } @Override diff --git a/CoreGraphNode/src/au/gov/asd/tac/constellation/graph/node/GraphNode.java b/CoreGraphNode/src/au/gov/asd/tac/constellation/graph/node/GraphNode.java index 26ff33a19d..b1d51c4b31 100644 --- a/CoreGraphNode/src/au/gov/asd/tac/constellation/graph/node/GraphNode.java +++ b/CoreGraphNode/src/au/gov/asd/tac/constellation/graph/node/GraphNode.java @@ -165,6 +165,7 @@ private GraphNode(final InstanceContent content, final Graph graph, final GraphD MemoryManager.newObject(GraphNode.class); } + @SuppressWarnings("deprecation") @Override public void finalize() { MemoryManager.finalizeObject(GraphNode.class); diff --git a/CoreGraphNode/src/au/gov/asd/tac/constellation/graph/node/gui/SimpleGraphTopComponent.java b/CoreGraphNode/src/au/gov/asd/tac/constellation/graph/node/gui/SimpleGraphTopComponent.java index e3fda3c82b..f0d3a8d86b 100644 --- a/CoreGraphNode/src/au/gov/asd/tac/constellation/graph/node/gui/SimpleGraphTopComponent.java +++ b/CoreGraphNode/src/au/gov/asd/tac/constellation/graph/node/gui/SimpleGraphTopComponent.java @@ -148,6 +148,7 @@ public void graphChanged(final GraphChangeEvent event) { } } + @SuppressWarnings("deprecation") @Override public void finalize() throws Throwable { try { diff --git a/CoreInteractiveGraph/nbproject/project.xml b/CoreInteractiveGraph/nbproject/project.xml index 479088483b..05a8469372 100644 --- a/CoreInteractiveGraph/nbproject/project.xml +++ b/CoreInteractiveGraph/nbproject/project.xml @@ -102,6 +102,14 @@ 1.0.6 + + au.gov.asd.tac.constellation.visual.vulkan + + + + 1.0 + + org.netbeans.modules.javahelp @@ -209,6 +217,7 @@ au.gov.asd.tac.constellation.graph.interaction.plugins.clipboard au.gov.asd.tac.constellation.graph.interaction.plugins.io au.gov.asd.tac.constellation.graph.interaction.plugins.zoom + au.gov.asd.tac.constellation.graph.interaction.visual diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/framework/GraphVisualManagerFactory.java b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/framework/GraphVisualManagerFactory.java index bcdfc1b751..be2f547bee 100644 --- a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/framework/GraphVisualManagerFactory.java +++ b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/framework/GraphVisualManagerFactory.java @@ -44,7 +44,8 @@ public abstract class GraphVisualManagerFactory { * * @param graph The graph to be visually managed. * @return A {@link VisualManager} for the graph. + * @throws java.lang.Throwable */ - public abstract VisualManager constructVisualManager(final Graph graph); + public abstract VisualManager constructVisualManager(final Graph graph) throws Throwable; } diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/framework/VisualAnnotator.java b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/framework/VisualAnnotator.java index 3a61fa330c..cc00c027e4 100644 --- a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/framework/VisualAnnotator.java +++ b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/framework/VisualAnnotator.java @@ -15,8 +15,9 @@ */ package au.gov.asd.tac.constellation.graph.interaction.framework; -import au.gov.asd.tac.constellation.graph.interaction.visual.renderables.NewLineModel; -import au.gov.asd.tac.constellation.graph.interaction.visual.renderables.SelectionBoxModel; +import au.gov.asd.tac.constellation.graph.hittest.HitState; +import au.gov.asd.tac.constellation.utilities.visual.NewLineModel; +import au.gov.asd.tac.constellation.utilities.visual.SelectionBoxModel; import au.gov.asd.tac.constellation.utilities.visual.VisualOperation; import au.gov.asd.tac.constellation.utilities.visual.VisualProcessor; import java.util.Queue; diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/framework/VisualInteraction.java b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/framework/VisualInteraction.java index e034e4e433..5a9b87feea 100644 --- a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/framework/VisualInteraction.java +++ b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/framework/VisualInteraction.java @@ -17,6 +17,7 @@ import au.gov.asd.tac.constellation.graph.GraphReadMethods; import au.gov.asd.tac.constellation.utilities.camera.Camera; +import au.gov.asd.tac.constellation.utilities.graphics.Vector2i; import au.gov.asd.tac.constellation.utilities.graphics.Vector3f; import java.awt.Point; @@ -148,4 +149,5 @@ public interface VisualInteraction { * @return A float value describing the scale factor */ public float getDPIScalingFactor(); + } diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/gui/VisualGraphTopComponent.java b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/gui/VisualGraphTopComponent.java index cfbf3c4eb2..8d8bce7c0d 100644 --- a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/gui/VisualGraphTopComponent.java +++ b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/gui/VisualGraphTopComponent.java @@ -104,6 +104,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -133,6 +135,7 @@ import org.openide.filesystems.FileUtil; import org.openide.loaders.DataObject; import org.openide.loaders.SaveAsCapable; +import org.openide.modules.Places; import org.openide.nodes.Node; import org.openide.util.Exceptions; import org.openide.util.HelpCtx; @@ -202,12 +205,12 @@ public final class VisualGraphTopComponent extends CloneableTopComponent impleme private static final String DISABLE_GRAPH_VISIBILITY_THRESHOLD_LABELS_SHORT_DESCRIPTION = "Disable Graph Visibility Threshold"; private final GraphVisualManagerFactory graphVisualManagerFactory; - private final VisualManager visualManager; - private final InstanceContent content; + private VisualManager visualManager; + private InstanceContent content; private final Graph graph; private MySaveAs saveAs = null; private MySavable savable = null; - private final GraphNode graphNode; + private GraphNode graphNode; /** * The countBase is the value of the counter at the most recent save when @@ -406,6 +409,31 @@ protected boolean update(GraphReadMethods updateState) { public UndoRedo getUndoRedo() { return graphNode.getUndoRedoManager(); } + + private GraphVisualManagerFactory GetVisualGraphFactory() { + // TODO: this needs to be rewritten to get the renderer factory class + // from config, firstly from file then from JAR. Preferrably by someone + // who has more of a clue about how Java resources work (ie anyone else). + + // Load render config in order to get visual manager factory class + Class visualMangerFactoryClass = GraphVisualManagerFactory.class; + try { + Path constellationRoot = Paths.get(Places.getUserDirectory().getPath(), "../..").toAbsolutePath().normalize(); + Path renderConfPath = Paths.get(constellationRoot.toString(), "renderer.conf"); + InputStream renderConfStream = new FileInputStream(renderConfPath.toString()); + System.getProperties().load(renderConfStream); + String factoryName = System.getProperty("factory"); + Class factoryClass = (Class)Class.forName(factoryName); + if (factoryClass != null) { + visualMangerFactoryClass = factoryClass; + } + } catch (Exception e) { + + } + + GraphVisualManagerFactory factory = Lookup.getDefault().lookup(visualMangerFactoryClass); + return factory; + } /** * Construct a new TopComponent with an empty graph. @@ -418,13 +446,21 @@ public VisualGraphTopComponent() { final GraphDataObject gdo = GraphObjectUtilities.createMemoryDataObject("graph", true); this.graph = new DualGraph(null); - graphVisualManagerFactory = Lookup.getDefault().lookup(GraphVisualManagerFactory.class); - visualManager = graphVisualManagerFactory.constructVisualManager(graph); - visualManager.startProcessing(); - graphNode = new GraphNode(graph, gdo, this, visualManager); - content = new InstanceContent(); - init(); - MemoryManager.newObject(VisualGraphTopComponent.class); + graphVisualManagerFactory = GetVisualGraphFactory(); + try { + // This may throw if we are constructing a Vulkan visual manager + visualManager = graphVisualManagerFactory.constructVisualManager(graph); + visualManager.startProcessing(); + graphNode = new GraphNode(graph, gdo, this, visualManager); + content = new InstanceContent(); + + init(); + MemoryManager.newObject(VisualGraphTopComponent.class); + } catch (Throwable t) { + visualManager = null; + graphNode = null; + content = null; + } } /** @@ -439,20 +475,26 @@ public VisualGraphTopComponent(final GraphDataObject gdo, final Graph graph) { setToolTipText(gdo.getToolTipText()); this.graph = graph; - graphVisualManagerFactory = Lookup.getDefault().lookup(GraphVisualManagerFactory.class); - visualManager = graphVisualManagerFactory.constructVisualManager(graph); - visualManager.startProcessing(); + graphVisualManagerFactory = GetVisualGraphFactory(); + try { + // This may throw if we are constructing a Vulkan visual manager + visualManager = graphVisualManagerFactory.constructVisualManager(graph); + visualManager.startProcessing(); - Schema schema = graph.getSchema(); - if (schema instanceof GraphNodeFactory) { - graphNode = ((GraphNodeFactory) schema).createGraphNode(graph, gdo, this, visualManager); - } else { - graphNode = new GraphNode(graph, gdo, this, visualManager); - } + Schema schema = graph.getSchema(); + if (schema instanceof GraphNodeFactory) { + graphNode = ((GraphNodeFactory) schema).createGraphNode(graph, gdo, this, visualManager); + } else { + graphNode = new GraphNode(graph, gdo, this, visualManager); + } + + content = new InstanceContent(); - content = new InstanceContent(); - init(); - MemoryManager.newObject(VisualGraphTopComponent.class); + init(); + MemoryManager.newObject(VisualGraphTopComponent.class); + } catch (Throwable t) { + System.out.print(t); + } } @Override @@ -573,6 +615,7 @@ public void graphChanged(final GraphChangeEvent evt) { } } + @SuppressWarnings("deprecation") @Override public void finalize() throws Throwable { try { diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/DefaultInteractionEventHandler.java b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/DefaultInteractionEventHandler.java index 56be35b06f..fa402bfc0a 100644 --- a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/DefaultInteractionEventHandler.java +++ b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/DefaultInteractionEventHandler.java @@ -21,8 +21,8 @@ import au.gov.asd.tac.constellation.graph.GraphWriteMethods; import au.gov.asd.tac.constellation.graph.WritableGraph; import au.gov.asd.tac.constellation.graph.interaction.InteractiveGraphPluginRegistry; -import au.gov.asd.tac.constellation.graph.interaction.framework.HitState; -import au.gov.asd.tac.constellation.graph.interaction.framework.HitState.HitType; +import au.gov.asd.tac.constellation.graph.hittest.HitState; +import au.gov.asd.tac.constellation.graph.hittest.HitState.HitType; import au.gov.asd.tac.constellation.graph.interaction.framework.InteractionEventHandler; import au.gov.asd.tac.constellation.graph.interaction.framework.VisualAnnotator; import au.gov.asd.tac.constellation.graph.interaction.framework.VisualInteraction; @@ -32,8 +32,8 @@ import au.gov.asd.tac.constellation.graph.interaction.plugins.select.PointSelectionPlugin; import au.gov.asd.tac.constellation.graph.interaction.visual.EventState.CreationMode; import au.gov.asd.tac.constellation.graph.interaction.visual.EventState.SceneAction; -import au.gov.asd.tac.constellation.graph.interaction.visual.renderables.NewLineModel; -import au.gov.asd.tac.constellation.graph.interaction.visual.renderables.SelectionBoxModel; +import au.gov.asd.tac.constellation.utilities.visual.NewLineModel; +import au.gov.asd.tac.constellation.utilities.visual.SelectionBoxModel; import au.gov.asd.tac.constellation.graph.schema.visual.concept.VisualConcept; import au.gov.asd.tac.constellation.graph.visual.contextmenu.ContextMenuProvider; import au.gov.asd.tac.constellation.graph.visual.utilities.VisualGraphUtilities; @@ -49,6 +49,7 @@ import au.gov.asd.tac.constellation.utilities.camera.Camera; import au.gov.asd.tac.constellation.utilities.camera.CameraUtilities; import au.gov.asd.tac.constellation.utilities.graphics.IntArray; +import au.gov.asd.tac.constellation.utilities.graphics.Vector2i; import au.gov.asd.tac.constellation.utilities.graphics.Vector3f; import au.gov.asd.tac.constellation.utilities.visual.VisualChangeBuilder; import au.gov.asd.tac.constellation.utilities.visual.VisualManager; @@ -417,7 +418,8 @@ public void mouseDragged(final MouseEvent event) { if (zAxisRotation) { CameraUtilities.spin(camera, visualInteraction.convertTranslationToSpin(from, to)); } else { - CameraUtilities.rotate(camera, event.isShiftDown() ? 0 : (from.y - to.y) / 2.0f, event.isControlDown() ? 0 : (from.x - to.x) / 2.0f); + Vector2i yawPitch = new Vector2i(from.x - to.x, from.y - to.y); + CameraUtilities.rotate(camera, event.isShiftDown() ? 0 : yawPitch.getY() / 2.0f, event.isControlDown() ? 0 : yawPitch.getX() / 2.0f); } cameraChange = true; break; @@ -1160,6 +1162,6 @@ private void scaleMousePointByDPIFactor(Point pointToScale) { // HACK_DPI - Get the DPI scale factor and multiply the point by it final float dpiScalingFactor = this.visualInteraction.getDPIScalingFactor(); pointToScale.x *= dpiScalingFactor; - pointToScale.y *= dpiScalingFactor; + pointToScale.y *= dpiScalingFactor; } } diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/EventState.java b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/EventState.java index 1c220f35a6..d2953c5483 100644 --- a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/EventState.java +++ b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/EventState.java @@ -16,7 +16,7 @@ package au.gov.asd.tac.constellation.graph.interaction.visual; import au.gov.asd.tac.constellation.graph.Graph; -import au.gov.asd.tac.constellation.graph.interaction.framework.HitState; +import au.gov.asd.tac.constellation.graph.hittest.HitState; import au.gov.asd.tac.constellation.utilities.graphics.Vector3f; import java.awt.Point; import java.util.HashMap; diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/GLInteractiveVisualManagerFactory.java b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/GLInteractiveVisualManagerFactory.java index 7e0553f28e..8b7c6ce21a 100644 --- a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/GLInteractiveVisualManagerFactory.java +++ b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/GLInteractiveVisualManagerFactory.java @@ -39,7 +39,7 @@ * * @author twilight_sparkle */ -@ServiceProvider(service = GraphVisualManagerFactory.class) +@ServiceProvider(service = GraphVisualManagerFactory.class, position = 1) public class GLInteractiveVisualManagerFactory extends GraphVisualManagerFactory { @Override diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/GraphRendererDropTarget.java b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/GraphRendererDropTarget.java index b5bdfcc3e0..aa8a3b708b 100644 --- a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/GraphRendererDropTarget.java +++ b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/GraphRendererDropTarget.java @@ -16,10 +16,10 @@ package au.gov.asd.tac.constellation.graph.interaction.visual; import au.gov.asd.tac.constellation.graph.Graph; -import au.gov.asd.tac.constellation.graph.interaction.framework.HitState; -import au.gov.asd.tac.constellation.graph.interaction.framework.HitState.HitType; import au.gov.asd.tac.constellation.graph.visual.dragdrop.GraphDropper; import au.gov.asd.tac.constellation.graph.visual.dragdrop.GraphDropper.DropInfo; +import au.gov.asd.tac.constellation.graph.hittest.HitState; +import au.gov.asd.tac.constellation.graph.hittest.HitState.HitType; import au.gov.asd.tac.constellation.utilities.graphics.Vector3f; import au.gov.asd.tac.constellation.utilities.visual.VisualManager; import java.awt.Point; diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/InteractiveGLVisualProcessor.java b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/InteractiveGLVisualProcessor.java index de8a10272f..cd4ec5a586 100644 --- a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/InteractiveGLVisualProcessor.java +++ b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/InteractiveGLVisualProcessor.java @@ -16,17 +16,17 @@ package au.gov.asd.tac.constellation.graph.interaction.visual; import au.gov.asd.tac.constellation.graph.GraphReadMethods; -import au.gov.asd.tac.constellation.graph.interaction.framework.HitState; -import au.gov.asd.tac.constellation.graph.interaction.framework.HitState.HitType; +import au.gov.asd.tac.constellation.graph.hittest.HitState; +import au.gov.asd.tac.constellation.graph.hittest.HitState.HitType; import au.gov.asd.tac.constellation.graph.interaction.framework.InteractionEventHandler; import au.gov.asd.tac.constellation.graph.interaction.framework.VisualAnnotator; import au.gov.asd.tac.constellation.graph.interaction.framework.VisualInteraction; -import au.gov.asd.tac.constellation.graph.interaction.visual.renderables.HitTestRequest; +import au.gov.asd.tac.constellation.graph.hittest.HitTestRequest; import au.gov.asd.tac.constellation.graph.interaction.visual.renderables.HitTester; -import au.gov.asd.tac.constellation.graph.interaction.visual.renderables.NewLineModel; +import au.gov.asd.tac.constellation.utilities.visual.NewLineModel; import au.gov.asd.tac.constellation.graph.interaction.visual.renderables.NewLineRenderable; import au.gov.asd.tac.constellation.graph.interaction.visual.renderables.PlanesRenderable; -import au.gov.asd.tac.constellation.graph.interaction.visual.renderables.SelectionBoxModel; +import au.gov.asd.tac.constellation.utilities.visual.SelectionBoxModel; import au.gov.asd.tac.constellation.graph.interaction.visual.renderables.SelectionBoxRenderable; import au.gov.asd.tac.constellation.graph.visual.utilities.VisualGraphUtilities; import au.gov.asd.tac.constellation.utilities.camera.Camera; diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/InteractiveVKVisualProcessor.java b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/InteractiveVKVisualProcessor.java new file mode 100644 index 0000000000..23782a25e8 --- /dev/null +++ b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/InteractiveVKVisualProcessor.java @@ -0,0 +1,325 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.graph.interaction.visual; + +import au.gov.asd.tac.constellation.graph.GraphReadMethods; +import au.gov.asd.tac.constellation.graph.hittest.HitState; +import au.gov.asd.tac.constellation.graph.hittest.HitState.HitType; +import au.gov.asd.tac.constellation.graph.interaction.framework.InteractionEventHandler; +import au.gov.asd.tac.constellation.graph.interaction.framework.VisualAnnotator; +import au.gov.asd.tac.constellation.graph.interaction.framework.VisualInteraction; +import au.gov.asd.tac.constellation.graph.hittest.HitTestRequest; +import au.gov.asd.tac.constellation.utilities.visual.NewLineModel; +import au.gov.asd.tac.constellation.utilities.visual.SelectionBoxModel; +import au.gov.asd.tac.constellation.graph.visual.utilities.VisualGraphUtilities; +import au.gov.asd.tac.constellation.utilities.camera.Camera; +import au.gov.asd.tac.constellation.utilities.camera.CameraUtilities; +import au.gov.asd.tac.constellation.utilities.camera.Graphics3DUtilities; +import au.gov.asd.tac.constellation.utilities.graphics.Matrix44f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector3f; +import au.gov.asd.tac.constellation.utilities.visual.VisualChangeBuilder; +import au.gov.asd.tac.constellation.utilities.visual.VisualOperation; +import au.gov.asd.tac.constellation.utilities.visual.VisualProperty; +import au.gov.asd.tac.constellation.visual.opengl.renderer.GLVisualProcessor; +import au.gov.asd.tac.constellation.utilities.visual.VisualChange; +import au.gov.asd.tac.constellation.visual.vulkan.CVKVisualProcessor; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssertNotNull; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.dnd.DropTarget; +import java.awt.dnd.DropTargetListener; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Queue; +import java.util.logging.Level; + +/** + * An extension of the {@link GLVisualProcessor} that adds support for user + * interaction through implementing {@link VisualInteraction} and + * {@link VisualAnnotator}. + *

+ * This provides the same basic visualisation of a graph as + * {@link GLVisualProcessor} does, adding renderables for a new line, a + * selection box, overlay planes, and hit testing. + * + * @author twilight_sparkle + */ +public class InteractiveVKVisualProcessor extends CVKVisualProcessor implements VisualInteraction, VisualAnnotator { + + private final long selectionBoxUpdateId = VisualChangeBuilder.generateNewId(); + private final long newLineUpdateId = VisualChangeBuilder.generateNewId(); + private final long greyscaleUpdateId = VisualChangeBuilder.generateNewId(); + private final long hitTestId = VisualChangeBuilder.generateNewId(); + private final long hitTestPointId = VisualChangeBuilder.generateNewId(); + private final TransformableGraphDisplayer graphDisplayer = new TransformableGraphDisplayer(); + + private InteractionEventHandler handler; + private DropTargetListener targetListener; + private DropTarget target; + + + public InteractiveVKVisualProcessor(final String graphId) throws Throwable { + super(graphId); + } + + /** + * Set the specified {@link InteractionEventHandler} to use this processor. + *

+ * This method adds the event handler as a listener (of all the relevant + * gesture types) to this processor's AWT component. + * + * @param handler The handler using this processor. + */ + public void setEventHandler(final InteractionEventHandler handler) { + this.handler = handler; + getCanvas().addKeyListener(this.handler); + getCanvas().addMouseListener(this.handler); + getCanvas().addMouseWheelListener(this.handler); + getCanvas().addMouseMotionListener(this.handler); + this.handler.startHandlingEvents(); + } + + @Override + protected void cleanup() { + super.cleanup(); + this.handler.stopHandlingEvents(); + if (target != null) { + target.removeDropTargetListener(targetListener); + } + } + + /** + * Add the specified {@link GraphRendererDropTarget} to this processor. + * + * @param targetListener The dropper using this processor. + * @return A {@link DropTarget} wrapping the supplied target and this + * processor's AWT component. + */ + public void addDropTargetToCanvas(final DropTargetListener targetListener) { + this.targetListener = targetListener; + target = new DropTarget(super.getCanvas(), this.targetListener); + } + + private final class GLSetHitTestingOperation implements VisualOperation { + + private final boolean doHitTesting; + + @Override + public int getPriority() { + return VisualOperation.VisualPriority.ELEVATED_VISUAL_PRIORITY.getValue(); + } + + public GLSetHitTestingOperation(final boolean doHitTesting) { + this.doHitTesting = doHitTesting; + } + + @Override + public void apply() { + //setDrawHitTest(doHitTesting); + } + + @Override + public List getVisualChanges() { + return Collections.emptyList(); + } + } + + @Override + public VisualOperation setHitTestingEnabled(boolean enabled) { + //throw new UnsupportedOperationException("setHitTestingEnabled shouldn't be needed for Vulkan"); + return new GLSetHitTestingOperation(enabled); + } + + @Override + public VisualOperation hitTestCursor(final int x, final int y, final HitState hitState, final Queue notificationQueue) { + if (cvkHitTester != null) { + cvkHitTester.queueRequest(new HitTestRequest(x, y, hitState, notificationQueue, resultState -> { + if (resultState.getCurrentHitType().equals(HitType.NO_ELEMENT)) { + getCanvas().setCursor(DEFAULT_CURSOR); + } else { + getCanvas().setCursor(CROSSHAIR_CURSOR); + } + })); + } + return () -> Arrays.asList(new VisualChangeBuilder(VisualProperty.EXTERNAL_CHANGE) + .withId(hitTestId).build()); + } + + @Override + public VisualOperation hitTestPoint(int x, int y, Queue notificationQueue) { + CVKAssertNotNull(cvkHitTester); + cvkHitTester.queueRequest(new HitTestRequest(x, y, new HitState(), notificationQueue, null)); + return () -> Arrays.asList(new VisualChangeBuilder(VisualProperty.EXTERNAL_CHANGE) + .withId(hitTestPointId).build()); + + } + + @Override + public VisualOperation setNewLineModel(NewLineModel model) { + cvkNewLine.queueModel(model); + return () -> Arrays.asList(new VisualChangeBuilder(VisualProperty.EXTERNAL_CHANGE) + .withId(newLineUpdateId).build()); + } + + @Override + public VisualOperation setSelectionBoxModel(SelectionBoxModel model) { + cvkSelectionBox.queueModel(model); + return () -> Arrays.asList(new VisualChangeBuilder(VisualProperty.EXTERNAL_CHANGE) + .withId(selectionBoxUpdateId).build()); + } + + @Override + public VisualOperation flagBusy(boolean isBusy) { + graphDisplayer.setGreyscale(isBusy); + return () -> Arrays.asList(new VisualChangeBuilder(VisualProperty.EXTERNAL_CHANGE) + .withId(greyscaleUpdateId).build()); + } + + @Override + public Vector3f convertZoomPointToDirection(final Point zoomPoint) { + + return new Vector3f( + (float) (getCanvas().getWidth() / 2.0 - zoomPoint.x), + + (float) (zoomPoint.y - getCanvas().getHeight() / 2.0), + (float) (getCanvas().getHeight() / (2 * Math.tan(Camera.FIELD_OF_VIEW * Math.PI / 180 / 2)))); + + } + + @Override + public Vector3f convertTranslationToDrag(final Camera camera, final Vector3f nodeLocation, final Point from, final Point to) { + // Calculate the vector representing the drag (in window coordinates) + final int dx = to.x - from.x; + final int dy = to.y - from.y; + final Vector3f movement = new Vector3f(dx, dy, 0); + + // Calculate and return the vector representing the drag in graph coordinates. + final Vector3f newposition = new Vector3f(); + Graphics3DUtilities.moveByProjection(nodeLocation, getCameraModelViewProjectionMatrix(camera), getViewport(), movement, newposition); + return newposition; + } + + @Override + public float convertTranslationToSpin(final Point from, final Point to) { + final float xDist = (getCanvas().getWidth() / 2.0f - to.x) / (getCanvas().getWidth() / 2.0f); + final float yDist = (getCanvas().getHeight() / 2.0f - to.y) / (getCanvas().getHeight() / 2.0f); + final float xDelta = (from.x - to.x) / 2.0f; + final float yDelta = (from.y - to.y) / 2.0f; + return yDist * xDelta - xDist * yDelta; + } + + @Override + public Vector3f convertTranslationToPan(final Point from, final Point to, final Vector3f panReferencePoint) { + final float dx = (to.x - from.x); + final float dy = (to.y - from.y); + + // Get the current screen height + final float screenHeight = getCanvas().getHeight(); + + // Calculate how many world units the camera needs to move per pixel + // This value is based on the current screen height and, for + // the nearest node from the cursor (when the mouse was pressed), + // the distance to the camera in the direction of the screen centre. + float worldUnitsPerPixel = (float) (panReferencePoint.getLength() * Math.tan(Math.toRadians(Camera.FIELD_OF_VIEW / 2.0)) * 2) / screenHeight; + + // Convert the pixel distances into world unit distances + return new Vector3f(-dx * worldUnitsPerPixel, dy * worldUnitsPerPixel, 0); + } + + @Override + public Vector3f windowToGraphCoordinates(final Camera camera, final Point point) { + final Camera originCamera = new Camera(camera); + CameraUtilities.moveEyeToOrigin(originCamera); + Matrix44f modelViewProjectionMatrix = getCameraModelViewProjectionMatrix(originCamera); + Vector3f worldPosition = new Vector3f(); + final Vector3f direction = CameraUtilities.getFocusVector(originCamera); + direction.scale(10); + + Vector3f screenPosition = new Vector3f(point.x, point.y, 0); + Graphics3DUtilities.screenToWorldCoordinates(screenPosition, direction, modelViewProjectionMatrix, getViewport(), worldPosition); + worldPosition.add(camera.lookAtEye); + return worldPosition; + } + + @Override + public Vector3f closestNodeCameraCoordinates(GraphReadMethods graph, Camera camera, Point p) { + + // Calculate the height and width of the viewing frustrum as a function of distance from the camera + final float verticalScale = (float) (Math.tan(Math.toRadians(Camera.FIELD_OF_VIEW / 2.0))); + final float horizontalScale = verticalScale * getCanvas().getWidth() / getCanvas().getHeight(); + float closestDistance = Float.MAX_VALUE; + Vector3f closestNode = null; + boolean foundScreenNode = false; + + // Iterate through the camera locations of each node in the graph + Iterator nodeLocations = VisualGraphUtilities.streamVertexSceneLocations(graph, camera).iterator(); + while (nodeLocations.hasNext()) { + + final Vector3f nodeLoaction = nodeLocations.next(); + final float zDistanceFromCamera = nodeLoaction.getZ(); + final float distanceFromCamera = nodeLoaction.getLength(); + + // Is the vertex in front of the camera? + if (zDistanceFromCamera < 0) { + final float horizontalOffset = nodeLoaction.getX() / zDistanceFromCamera; + final float verticalOffset = nodeLoaction.getY() / zDistanceFromCamera; + + // Is this vertex visible on the screen? + if (horizontalOffset > -horizontalScale && horizontalOffset < horizontalScale && verticalOffset > -verticalScale && verticalOffset < verticalScale) { + // Is the first or closest node visible on the screen, record it as the closest node + if (!foundScreenNode || distanceFromCamera < closestDistance) { + closestNode = nodeLoaction; + closestDistance = closestNode.getLength(); + foundScreenNode = true; + } + } else if (!foundScreenNode && distanceFromCamera < closestDistance) { + // If no vertices on the screen have been found, this vertex is in front of the camera, and is the closest (or first) such vertex, record it as the closest node. + closestNode = nodeLoaction; + closestDistance = closestNode.getLength(); + } + } + } + return closestNode; + } + + @Override + public float[] windowBoxToCameraBox(int left, int right, int top, int bottom) { + final float verticalScale = (float) (Math.tan(Math.toRadians(Camera.FIELD_OF_VIEW / 2.0))); + final float horizontalScale = verticalScale * getCanvas().getWidth() / getCanvas().getHeight(); + final int[] viewport = getViewport(); + final float leftScale = (((float) left / (float) viewport[2]) - 0.5f) * horizontalScale * 2; + final float rightScale = (((float) right / (float) viewport[2]) - 0.5f) * horizontalScale * 2; + final float topScale = (((float) (viewport[3] - top) / (float) viewport[3]) - 0.5f) * verticalScale * 2; + final float bottomScale = (((float) (viewport[3] - bottom) / (float) viewport[3]) - 0.5f) * verticalScale * 2; + return new float[]{leftScale, rightScale, topScale, bottomScale}; + } + + + @Override + public float getDPIScalingFactor() { + // HACK_DPI - Get the X Scale value from the Canvas's transform matrix + // This method was derived from the JOGL post found here: + // http://forum.jogamp.org162/canvas-not-filling-frame-td4040092.html#a4040210 + try { + return (float) ((Graphics2D) getCanvas().getGraphics()).getTransform().getScaleX(); + } catch (Exception ex) { + cvkLogger.log(Level.WARNING, "Null exception accessing interactionGraph", ex); + return 1.0f; + } + } +} diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/VKInteractiveVisualManagerFactory.java b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/VKInteractiveVisualManagerFactory.java new file mode 100644 index 0000000000..f4139eaf77 --- /dev/null +++ b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/VKInteractiveVisualManagerFactory.java @@ -0,0 +1,50 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.graph.interaction.visual; + +import au.gov.asd.tac.constellation.graph.Graph; +import au.gov.asd.tac.constellation.graph.interaction.framework.GraphVisualManagerFactory; +import au.gov.asd.tac.constellation.graph.monitor.GraphChangeListener; +import au.gov.asd.tac.constellation.graph.visual.framework.GraphVisualAccess; +import au.gov.asd.tac.constellation.preferences.DeveloperPreferenceKeys; +import au.gov.asd.tac.constellation.utilities.visual.VisualAccess; +import au.gov.asd.tac.constellation.utilities.visual.VisualManager; +import java.util.prefs.Preferences; +import org.openide.util.NbPreferences; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider(service = GraphVisualManagerFactory.class) +public class VKInteractiveVisualManagerFactory extends GraphVisualManagerFactory { + + @Override + public VisualManager constructVisualManager(Graph graph) throws Throwable { + final VisualAccess access = new GraphVisualAccess(graph); + final Preferences prefs = NbPreferences.forModule(DeveloperPreferenceKeys.class); + final InteractiveVKVisualProcessor processor = new InteractiveVKVisualProcessor(graph.getId()); + final VisualManager manager = new VisualManager(access, processor); + final GraphChangeListener changeDetector = event -> manager.updateFromIndigenousChanges(); + final DefaultInteractionEventHandler eventHandler = new DefaultInteractionEventHandler(graph, manager, processor, processor); + + //TODO_TT: +// processor.addDropTargetToCanvas(new GraphRendererDropTarget(graph, manager, processor)); + graph.addGraphChangeListener(changeDetector); + changeDetector.graphChanged(null); + processor.startVisualising(manager); + processor.setEventHandler(eventHandler); + + return manager; + } +} diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/HitTester.java b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/HitTester.java index 92dc7bd7d8..cfed5d07f5 100644 --- a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/HitTester.java +++ b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/HitTester.java @@ -15,8 +15,9 @@ */ package au.gov.asd.tac.constellation.graph.interaction.visual.renderables; -import au.gov.asd.tac.constellation.graph.interaction.framework.HitState; -import au.gov.asd.tac.constellation.graph.interaction.framework.HitState.HitType; +import au.gov.asd.tac.constellation.graph.hittest.HitTestRequest; +import au.gov.asd.tac.constellation.graph.hittest.HitState; +import au.gov.asd.tac.constellation.graph.hittest.HitState.HitType; import au.gov.asd.tac.constellation.utilities.graphics.Matrix44f; import au.gov.asd.tac.constellation.visual.opengl.renderer.GLRenderable; import au.gov.asd.tac.constellation.visual.opengl.renderer.GLVisualProcessor; diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/NewLineRenderable.java b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/NewLineRenderable.java index 4b7c5b3bfb..31f0a8e827 100644 --- a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/NewLineRenderable.java +++ b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/NewLineRenderable.java @@ -15,6 +15,7 @@ */ package au.gov.asd.tac.constellation.graph.interaction.visual.renderables; +import au.gov.asd.tac.constellation.utilities.visual.NewLineModel; import static au.gov.asd.tac.constellation.graph.interaction.visual.renderables.NewLineRenderable.NEW_LINE_COLOR; import static au.gov.asd.tac.constellation.graph.interaction.visual.renderables.NewLineRenderable.NEW_LINE_WIDTH; import au.gov.asd.tac.constellation.utilities.camera.Camera; diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/SelectionBoxRenderable.java b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/SelectionBoxRenderable.java index 2a39950237..44cdc397db 100644 --- a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/SelectionBoxRenderable.java +++ b/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/SelectionBoxRenderable.java @@ -15,6 +15,7 @@ */ package au.gov.asd.tac.constellation.graph.interaction.visual.renderables; +import au.gov.asd.tac.constellation.utilities.visual.SelectionBoxModel; import au.gov.asd.tac.constellation.utilities.graphics.Matrix44f; import au.gov.asd.tac.constellation.utilities.graphics.Vector3f; import au.gov.asd.tac.constellation.utilities.graphics.Vector4f; diff --git a/CoreNamedSelectionView/src/au/gov/asd/tac/constellation/views/namedselection/NamedSelectionTopComponent.java b/CoreNamedSelectionView/src/au/gov/asd/tac/constellation/views/namedselection/NamedSelectionTopComponent.java index 2d7f098157..7d013ed52f 100644 --- a/CoreNamedSelectionView/src/au/gov/asd/tac/constellation/views/namedselection/NamedSelectionTopComponent.java +++ b/CoreNamedSelectionView/src/au/gov/asd/tac/constellation/views/namedselection/NamedSelectionTopComponent.java @@ -144,6 +144,7 @@ else if (e.getKeyCode() == KeyEvent.VK_F2) { /** * Constructs a new NamedSelectionTopComponent. */ + @SuppressWarnings("unchecked") public NamedSelectionTopComponent() { initComponents(); diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/Bundle.properties b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/Bundle.properties index b3d2eba4cb..b061111697 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/Bundle.properties +++ b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/Bundle.properties @@ -1,4 +1,4 @@ OpenIDE-Module-Display-Category=Core OpenIDE-Module-Long-Description=The OpenGL graph renderer. -OpenIDE-Module-Name=Core OpenGL Display +OpenIDE-Module-Name=Core Display OpenGL OpenIDE-Module-Short-Description=Core OpenGL Display diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/GraphRenderable.java b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/GraphRenderable.java index 3492bff8a7..e4ff050ff2 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/GraphRenderable.java +++ b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/GraphRenderable.java @@ -15,6 +15,7 @@ */ package au.gov.asd.tac.constellation.visual.opengl.renderer; +import au.gov.asd.tac.constellation.utilities.visual.DirectionIndicatorsAction; import au.gov.asd.tac.constellation.utilities.camera.Camera; import au.gov.asd.tac.constellation.utilities.camera.Graphics3DUtilities; import au.gov.asd.tac.constellation.utilities.color.ConstellationColor; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/batcher/ConnectionLabelBatcher.java b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/batcher/ConnectionLabelBatcher.java index 2b77707b77..768351c28a 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/batcher/ConnectionLabelBatcher.java +++ b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/batcher/ConnectionLabelBatcher.java @@ -22,10 +22,10 @@ import au.gov.asd.tac.constellation.utilities.visual.VisualAccess; import au.gov.asd.tac.constellation.visual.opengl.renderer.GLRenderable.GLRenderableUpdateTask; import au.gov.asd.tac.constellation.visual.opengl.renderer.TextureUnits; -import au.gov.asd.tac.constellation.visual.opengl.utilities.LabelUtilities; +import au.gov.asd.tac.constellation.utilities.text.LabelUtilities; import au.gov.asd.tac.constellation.visual.opengl.utilities.SharedDrawable; import au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs.ConnectionGlyphStream; -import au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs.ConnectionGlyphStreamContext; +import au.gov.asd.tac.constellation.utilities.glyphs.ConnectionGlyphStreamContext; import com.jogamp.opengl.GL; import com.jogamp.opengl.GL3; import java.io.IOException; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/batcher/LineBatcher.java b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/batcher/LineBatcher.java index 140d60a638..08bdb22961 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/batcher/LineBatcher.java +++ b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/batcher/LineBatcher.java @@ -23,7 +23,7 @@ import au.gov.asd.tac.constellation.utilities.visual.VisualChange; import au.gov.asd.tac.constellation.visual.opengl.renderer.GLRenderable.GLRenderableUpdateTask; import au.gov.asd.tac.constellation.visual.opengl.renderer.TextureUnits; -import au.gov.asd.tac.constellation.visual.opengl.utilities.LabelUtilities; +import au.gov.asd.tac.constellation.utilities.text.LabelUtilities; import au.gov.asd.tac.constellation.visual.opengl.utilities.SharedDrawable; import com.jogamp.common.nio.Buffers; import com.jogamp.opengl.GL3; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/batcher/NodeLabelBatcher.java b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/batcher/NodeLabelBatcher.java index 5e99328551..18997aa86f 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/batcher/NodeLabelBatcher.java +++ b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/batcher/NodeLabelBatcher.java @@ -21,10 +21,10 @@ import au.gov.asd.tac.constellation.utilities.visual.VisualAccess; import au.gov.asd.tac.constellation.visual.opengl.renderer.GLRenderable.GLRenderableUpdateTask; import au.gov.asd.tac.constellation.visual.opengl.renderer.TextureUnits; -import au.gov.asd.tac.constellation.visual.opengl.utilities.LabelUtilities; +import au.gov.asd.tac.constellation.utilities.text.LabelUtilities; import au.gov.asd.tac.constellation.visual.opengl.utilities.SharedDrawable; import au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs.NodeGlyphStream; -import au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs.NodeGlyphStreamContext; +import au.gov.asd.tac.constellation.utilities.glyphs.NodeGlyphStreamContext; import com.jogamp.opengl.GL3; import java.io.IOException; import java.nio.FloatBuffer; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelFontsOptionsPanel.java b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelFontsOptionsPanel.java index 6c9e460267..af975c5068 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelFontsOptionsPanel.java +++ b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelFontsOptionsPanel.java @@ -17,10 +17,10 @@ import au.gov.asd.tac.constellation.utilities.gui.JNumberedTextArea; import au.gov.asd.tac.constellation.utilities.text.SeparatorConstants; -import au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs.ConstellationLabelFonts; -import au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs.FontInfo; -import au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs.FontStyle; -import au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs.GlyphManagerBI; +import au.gov.asd.tac.constellation.utilities.glyphs.ConstellationLabelFonts; +import au.gov.asd.tac.constellation.utilities.glyphs.FontInfo; +import au.gov.asd.tac.constellation.utilities.glyphs.FontStyle; +import au.gov.asd.tac.constellation.utilities.glyphs.GlyphManagerBI; import java.awt.Color; import java.awt.Component; import java.util.Arrays; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelFontsOptionsPanelController.java b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelFontsOptionsPanelController.java index 2ab673bfd8..c50563452a 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelFontsOptionsPanelController.java +++ b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelFontsOptionsPanelController.java @@ -15,6 +15,7 @@ */ package au.gov.asd.tac.constellation.visual.opengl.utilities; +import au.gov.asd.tac.constellation.utilities.glyphs.LabelFontsPreferenceKeys; import java.awt.GraphicsEnvironment; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/SharedDrawable.java b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/SharedDrawable.java index 54f8b2566a..f523fb2b98 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/SharedDrawable.java +++ b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/SharedDrawable.java @@ -15,10 +15,11 @@ */ package au.gov.asd.tac.constellation.visual.opengl.utilities; +import au.gov.asd.tac.constellation.utilities.glyphs.LabelFontsPreferenceKeys; import au.gov.asd.tac.constellation.utilities.text.SeparatorConstants; import au.gov.asd.tac.constellation.visual.opengl.renderer.GLVisualProcessor; -import au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs.GlyphManager; -import au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs.GlyphManagerBI; +import au.gov.asd.tac.constellation.utilities.glyphs.GlyphManager; +import au.gov.asd.tac.constellation.utilities.glyphs.GlyphManagerBI; import au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs.GlyphManagerOpenGLController; import com.jogamp.opengl.DebugGL3; import com.jogamp.opengl.GL3; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/ConnectionGlyphStream.java b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/ConnectionGlyphStream.java index 2a90fc4617..2f1a1d0524 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/ConnectionGlyphStream.java +++ b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/ConnectionGlyphStream.java @@ -15,6 +15,9 @@ */ package au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs; +import au.gov.asd.tac.constellation.utilities.glyphs.ConnectionGlyphStreamContext; +import au.gov.asd.tac.constellation.utilities.glyphs.GlyphManager; +import au.gov.asd.tac.constellation.utilities.glyphs.GlyphStreamContext; import au.gov.asd.tac.constellation.utilities.graphics.FloatArray; import au.gov.asd.tac.constellation.utilities.graphics.IntArray; import au.gov.asd.tac.constellation.visual.opengl.renderer.batcher.ConnectionLabelBatcher; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/DefaultLabelFonts.java b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/DefaultLabelFonts.java index 83281ec188..045df83969 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/DefaultLabelFonts.java +++ b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/DefaultLabelFonts.java @@ -15,6 +15,7 @@ */ package au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs; +import au.gov.asd.tac.constellation.utilities.glyphs.ConstellationLabelFonts; import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphManagerOpenGLController.java b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphManagerOpenGLController.java index 1a600f6a81..734f6585df 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphManagerOpenGLController.java +++ b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphManagerOpenGLController.java @@ -15,6 +15,7 @@ */ package au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs; +import au.gov.asd.tac.constellation.utilities.glyphs.GlyphManager; import com.jogamp.opengl.GL3; import java.nio.ByteBuffer; import java.nio.FloatBuffer; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/NodeGlyphStream.java b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/NodeGlyphStream.java index bb1ff2a9ad..44829e44e9 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/NodeGlyphStream.java +++ b/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/NodeGlyphStream.java @@ -15,6 +15,9 @@ */ package au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs; +import au.gov.asd.tac.constellation.utilities.glyphs.NodeGlyphStreamContext; +import au.gov.asd.tac.constellation.utilities.glyphs.GlyphManager; +import au.gov.asd.tac.constellation.utilities.glyphs.GlyphStreamContext; import au.gov.asd.tac.constellation.utilities.graphics.FloatArray; import au.gov.asd.tac.constellation.utilities.graphics.IntArray; import au.gov.asd.tac.constellation.visual.opengl.utilities.SharedDrawable; diff --git a/CoreOpenGLDisplay/test/unit/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelUtilitiesNGTest.java b/CoreOpenGLDisplay/test/unit/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelUtilitiesNGTest.java index 2b2b152fc3..a3bf9b69c1 100644 --- a/CoreOpenGLDisplay/test/unit/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelUtilitiesNGTest.java +++ b/CoreOpenGLDisplay/test/unit/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelUtilitiesNGTest.java @@ -15,6 +15,7 @@ */ package au.gov.asd.tac.constellation.visual.opengl.utilities; +import au.gov.asd.tac.constellation.utilities.text.LabelUtilities; import java.util.ArrayList; import java.util.List; import static org.testng.Assert.assertEquals; diff --git a/CoreOpenGLDisplay/test/unit/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphRectangleBufferNGTest.java b/CoreOpenGLDisplay/test/unit/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphRectangleBufferNGTest.java index 79365a41dc..4d5d1c928f 100644 --- a/CoreOpenGLDisplay/test/unit/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphRectangleBufferNGTest.java +++ b/CoreOpenGLDisplay/test/unit/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphRectangleBufferNGTest.java @@ -15,7 +15,8 @@ */ package au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs; -import static au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs.GlyphManagerBI.DEFAULT_BUFFER_TYPE; +import au.gov.asd.tac.constellation.utilities.glyphs.GlyphRectangleBuffer; +import static au.gov.asd.tac.constellation.utilities.glyphs.GlyphManagerBI.DEFAULT_BUFFER_TYPE; import java.awt.Color; import java.awt.Graphics2D; import java.awt.RenderingHints; diff --git a/CorePluginFramework/src/au/gov/asd/tac/constellation/plugins/parameters/PluginParameter.java b/CorePluginFramework/src/au/gov/asd/tac/constellation/plugins/parameters/PluginParameter.java index e576fb7b61..7ecf4e412c 100644 --- a/CorePluginFramework/src/au/gov/asd/tac/constellation/plugins/parameters/PluginParameter.java +++ b/CorePluginFramework/src/au/gov/asd/tac/constellation/plugins/parameters/PluginParameter.java @@ -427,6 +427,7 @@ public void removeListener(final PluginParameterListener listener) { * * @return A new instance of PluginParameter. */ + @SuppressWarnings({"unchecked", "rawtypes"}) protected PluginParameter create(final ParameterValue value, final PluginParameterType type, final String id) { final PluginParameter p = new PluginParameter(value.copy(), type, id); if (p.value instanceof ParameterListParameterValue) { diff --git a/CoreQualityControlView/src/au/gov/asd/tac/constellation/views/qualitycontrol/QualityControlViewPane.java b/CoreQualityControlView/src/au/gov/asd/tac/constellation/views/qualitycontrol/QualityControlViewPane.java index 7b340e3bee..349e089991 100644 --- a/CoreQualityControlView/src/au/gov/asd/tac/constellation/views/qualitycontrol/QualityControlViewPane.java +++ b/CoreQualityControlView/src/au/gov/asd/tac/constellation/views/qualitycontrol/QualityControlViewPane.java @@ -199,6 +199,7 @@ public QualityControlViewPane() { * * @param state The new state to display in the view. */ + @SuppressWarnings({"unchecked", "rawtypes"}) public void refreshQualityControlView(final QualityControlState state) { Platform.runLater(() -> { final ProgressIndicator progress = new ProgressIndicator(); diff --git a/CoreSchemaView/src/au/gov/asd/tac/constellation/views/schemaview/providers/PluginsNodeProvider.java b/CoreSchemaView/src/au/gov/asd/tac/constellation/views/schemaview/providers/PluginsNodeProvider.java index ef2eda89f6..c6e37e155a 100644 --- a/CoreSchemaView/src/au/gov/asd/tac/constellation/views/schemaview/providers/PluginsNodeProvider.java +++ b/CoreSchemaView/src/au/gov/asd/tac/constellation/views/schemaview/providers/PluginsNodeProvider.java @@ -81,6 +81,7 @@ public PluginsNodeProvider() { pane = new VBox(); } + @SuppressWarnings("unchecked") @Override public void setContent(final Tab tab) { final Map pluginNames = new TreeMap<>(); diff --git a/CoreScriptingView/src/au/gov/asd/tac/constellation/views/scripting/ScriptingViewTopComponent.java b/CoreScriptingView/src/au/gov/asd/tac/constellation/views/scripting/ScriptingViewTopComponent.java index 90b2533242..d673eb229a 100644 --- a/CoreScriptingView/src/au/gov/asd/tac/constellation/views/scripting/ScriptingViewTopComponent.java +++ b/CoreScriptingView/src/au/gov/asd/tac/constellation/views/scripting/ScriptingViewTopComponent.java @@ -51,6 +51,7 @@ "CTL_ScriptingViewAction=Scripting View", "CTL_ScriptingViewTopComponent=Scripting View", "HINT_ScriptingViewTopComponent=Scripting View"}) +@SuppressWarnings("rawtypes") public final class ScriptingViewTopComponent extends SwingTopComponent { private final ScriptingViewPane scriptingViewPane; diff --git a/CoreTesting/nbproject/genfiles.properties b/CoreTesting/nbproject/genfiles.properties deleted file mode 100644 index 27dd0c8806..0000000000 --- a/CoreTesting/nbproject/genfiles.properties +++ /dev/null @@ -1,8 +0,0 @@ -build.xml.data.CRC32=5c21f638 -build.xml.script.CRC32=b5f9ba5a -build.xml.stylesheet.CRC32=15ca8a54@2.79 -# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. -# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. -nbproject/build-impl.xml.data.CRC32=5c21f638 -nbproject/build-impl.xml.script.CRC32=b7b1c265 -nbproject/build-impl.xml.stylesheet.CRC32=49aa68b0@2.79 diff --git a/CoreTesting/nbproject/project.xml b/CoreTesting/nbproject/project.xml index 66d617bf3a..a95d697c99 100644 --- a/CoreTesting/nbproject/project.xml +++ b/CoreTesting/nbproject/project.xml @@ -126,6 +126,14 @@ 1.0.6 + + au.gov.asd.tac.constellation.visual.vulkan + + + + 1.0 + + org.netbeans.libs.testng diff --git a/CoreTesting/src/au/gov/asd/tac/constellation/testing/ExportIconTextureAtlasAction.java b/CoreTesting/src/au/gov/asd/tac/constellation/testing/ExportIconTextureAtlasAction.java new file mode 100644 index 0000000000..40f9b3704c --- /dev/null +++ b/CoreTesting/src/au/gov/asd/tac/constellation/testing/ExportIconTextureAtlasAction.java @@ -0,0 +1,56 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.testing; + +import au.gov.asd.tac.constellation.visual.vulkan.CVKVisualProcessor; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import javax.swing.JFileChooser; +import javax.swing.filechooser.FileNameExtensionFilter; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionRegistration; +import org.openide.util.NbBundle; + + +@ActionID( + category = "Experimental", + id = "au.gov.asd.tac.constellation.testing.ExportIconTextureAtlasAction" +) +@ActionRegistration( + displayName = "#CTL_ExportIconTextureAtlasAction" +) +@ActionReference(path = "Menu/Experimental/Developer", position = 0) +@NbBundle.Messages("CTL_ExportIconTextureAtlasAction=Export Icon Atlas") +public final class ExportIconTextureAtlasAction implements ActionListener { + + @Override + public void actionPerformed(ActionEvent e) { + final JFileChooser fileChooser = new JFileChooser(); + fileChooser.setMultiSelectionEnabled(false); + fileChooser.setFileFilter(new FileNameExtensionFilter("PNG Images", "png")); + if (fileChooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { + File file = fileChooser.getSelectedFile(); + if (!file.getName().endsWith(".png")) { + String fileName = file.getName() + ".png"; + file = new File(file.getParent(), fileName); + } + + CVKVisualProcessor.ExportIconTextureAtlas(file); + } + } +} diff --git a/CoreTesting/src/au/gov/asd/tac/constellation/testing/ToggleContinuousRenderingAction.java b/CoreTesting/src/au/gov/asd/tac/constellation/testing/ToggleContinuousRenderingAction.java new file mode 100644 index 0000000000..9134bc8de7 --- /dev/null +++ b/CoreTesting/src/au/gov/asd/tac/constellation/testing/ToggleContinuousRenderingAction.java @@ -0,0 +1,42 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.testing; + +import au.gov.asd.tac.constellation.utilities.visual.VisualManager; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import org.openide.awt.ActionID; +import org.openide.awt.ActionReference; +import org.openide.awt.ActionRegistration; +import org.openide.util.NbBundle; + + +@ActionID( + category = "Experimental", + id = "au.gov.asd.tac.constellation.testing.ToggleContinuousRenderingAction" +) +@ActionRegistration( + displayName = "#CTL_ToggleContinuousRenderingAction" +) +@ActionReference(path = "Menu/Experimental/Developer", position = 0) +@NbBundle.Messages("CTL_ToggleContinuousRenderingAction=Toggle continuous rendering") +public final class ToggleContinuousRenderingAction implements ActionListener { + + @Override + public void actionPerformed(ActionEvent e) { + VisualManager.ToggleContinuousRedraw(); + } +} \ No newline at end of file diff --git a/CoreTimelineView/src/au/gov/asd/tac/constellation/views/timeline/OverviewPanel.java b/CoreTimelineView/src/au/gov/asd/tac/constellation/views/timeline/OverviewPanel.java index fdc746b083..2056ae81f3 100644 --- a/CoreTimelineView/src/au/gov/asd/tac/constellation/views/timeline/OverviewPanel.java +++ b/CoreTimelineView/src/au/gov/asd/tac/constellation/views/timeline/OverviewPanel.java @@ -175,6 +175,7 @@ public void setExtentPOV(double lowerBound, double upperBound) { * @param isFullRefresh is a full refresh needed. * @param selectedOnly only show selected items. */ + @SuppressWarnings("unchecked") public void populateHistogram(final ReadableGraph graph, final String datetimeAttribute, final double lowestTimeExtent, final double highestTimeExtent, final boolean isFullRefresh, final boolean selectedOnly) { diff --git a/CoreTimelineView/src/au/gov/asd/tac/constellation/views/timeline/components/TimelineChart.java b/CoreTimelineView/src/au/gov/asd/tac/constellation/views/timeline/components/TimelineChart.java index effc564f4e..26a042e262 100644 --- a/CoreTimelineView/src/au/gov/asd/tac/constellation/views/timeline/components/TimelineChart.java +++ b/CoreTimelineView/src/au/gov/asd/tac/constellation/views/timeline/components/TimelineChart.java @@ -404,6 +404,7 @@ public StringProperty upperTimeExtentProperty() { * @param lowestObservedDisplayPos Sets the lowest yAxis value. * @param highestObservedDisplayPos Sets the highest yAxis value. */ + @SuppressWarnings("unchecked") public void populate(final XYChart.Series series, final long lowestObservedDisplayPos, final long highestObservedDisplayPos, final boolean selectedOnly, final ZoneId zoneId) { this.selectedOnly = selectedOnly; this.currentTimezone = TimeZone.getTimeZone(zoneId); diff --git a/CoreUtilities/nbproject/genfiles.properties b/CoreUtilities/nbproject/genfiles.properties index 45d2cfde4f..cff7cf4ce0 100644 --- a/CoreUtilities/nbproject/genfiles.properties +++ b/CoreUtilities/nbproject/genfiles.properties @@ -1,8 +1,8 @@ -build.xml.data.CRC32=0166acf7 +build.xml.data.CRC32=7c54c193 build.xml.script.CRC32=686453dc -build.xml.stylesheet.CRC32=15ca8a54@2.79 +build.xml.stylesheet.CRC32=15ca8a54@2.80 # This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. # Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. -nbproject/build-impl.xml.data.CRC32=0166acf7 +nbproject/build-impl.xml.data.CRC32=7c54c193 nbproject/build-impl.xml.script.CRC32=ef709e25 -nbproject/build-impl.xml.stylesheet.CRC32=49aa68b0@2.79 +nbproject/build-impl.xml.stylesheet.CRC32=49aa68b0@2.80 diff --git a/CoreUtilities/nbproject/project.xml b/CoreUtilities/nbproject/project.xml index 07ba2a3f87..32549f45b7 100644 --- a/CoreUtilities/nbproject/project.xml +++ b/CoreUtilities/nbproject/project.xml @@ -142,6 +142,7 @@ au.gov.asd.tac.constellation.utilities.font au.gov.asd.tac.constellation.utilities.genericjsonio au.gov.asd.tac.constellation.utilities.geospatial + au.gov.asd.tac.constellation.utilities.glyphs au.gov.asd.tac.constellation.utilities.graphics au.gov.asd.tac.constellation.utilities.gui au.gov.asd.tac.constellation.utilities.html diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/ConnectionGlyphStreamContext.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/ConnectionGlyphStreamContext.java similarity index 78% rename from CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/ConnectionGlyphStreamContext.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/ConnectionGlyphStreamContext.java index 2b0f00d159..190c78ab7a 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/ConnectionGlyphStreamContext.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/ConnectionGlyphStreamContext.java @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs; +package au.gov.asd.tac.constellation.utilities.glyphs; + +import au.gov.asd.tac.constellation.utilities.glyphs.GlyphStreamContext; /** * @@ -27,6 +29,7 @@ public class ConnectionGlyphStreamContext extends GlyphStreamContext{ public int currentOffset; public int nextLeftOffset; public int nextRightOffset; + public boolean isAttributeLabel; public ConnectionGlyphStreamContext() { super(); @@ -36,6 +39,11 @@ public ConnectionGlyphStreamContext(int totalScale, float visibility, int labelN super(totalScale, visibility, labelNumber); } + public ConnectionGlyphStreamContext(int totalScale, float visibility, int labelNumber, boolean isAttributeLabel) { + super(totalScale, visibility, labelNumber); + this.isAttributeLabel = isAttributeLabel; + } + public ConnectionGlyphStreamContext(ConnectionGlyphStreamContext context) { currentLowNodeId = context.currentLowNodeId; currentHighNodeId = context.currentHighNodeId; @@ -44,5 +52,6 @@ public ConnectionGlyphStreamContext(ConnectionGlyphStreamContext context) { currentOffset = context.currentOffset; nextLeftOffset = context.nextLeftOffset; nextRightOffset = context.nextRightOffset; + isAttributeLabel = context.isAttributeLabel; } } diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/ConstellationLabelFonts.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/ConstellationLabelFonts.java similarity index 96% rename from CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/ConstellationLabelFonts.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/ConstellationLabelFonts.java index 74a4ac6127..d6e0539e08 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/ConstellationLabelFonts.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/ConstellationLabelFonts.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs; +package au.gov.asd.tac.constellation.utilities.glyphs; import au.gov.asd.tac.constellation.utilities.text.SeparatorConstants; import java.util.List; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/FontDirectionalRun.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/FontDirectionalRun.java similarity index 99% rename from CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/FontDirectionalRun.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/FontDirectionalRun.java index 57f5b0fbdc..4d0efcee87 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/FontDirectionalRun.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/FontDirectionalRun.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs; +package au.gov.asd.tac.constellation.utilities.glyphs; import java.awt.Font; import java.awt.font.TextAttribute; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/FontInfo.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/FontInfo.java similarity index 99% rename from CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/FontInfo.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/FontInfo.java index 6694ddbc4e..0ef6133879 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/FontInfo.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/FontInfo.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs; +package au.gov.asd.tac.constellation.utilities.glyphs; import au.gov.asd.tac.constellation.utilities.text.SeparatorConstants; import java.awt.Font; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/FontRunSequence.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/FontRunSequence.java similarity index 98% rename from CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/FontRunSequence.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/FontRunSequence.java index 3b822c45c9..cd32ceca28 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/FontRunSequence.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/FontRunSequence.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs; +package au.gov.asd.tac.constellation.utilities.glyphs; import java.awt.Font; import java.util.ArrayList; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/FontStyle.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/FontStyle.java similarity index 91% rename from CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/FontStyle.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/FontStyle.java index c9c6163bb8..4d54d0e812 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/FontStyle.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/FontStyle.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs; +package au.gov.asd.tac.constellation.utilities.glyphs; /** * An enumeration for font styles. diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphManager.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphManager.java similarity index 98% rename from CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphManager.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphManager.java index b8a753a29c..8819b64e92 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphManager.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphManager.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs; +package au.gov.asd.tac.constellation.utilities.glyphs; import java.io.IOException; import java.io.OutputStream; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphManagerBI.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphManagerBI.java similarity index 99% rename from CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphManagerBI.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphManagerBI.java index 60804e6fbc..221c572a25 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphManagerBI.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphManagerBI.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs; +package au.gov.asd.tac.constellation.utilities.glyphs; import java.awt.Color; import java.awt.Font; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphManagerFX.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphManagerFX.java similarity index 99% rename from CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphManagerFX.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphManagerFX.java index cf74c1ac94..096ca05117 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphManagerFX.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphManagerFX.java @@ -1,3 +1,5 @@ +package au.gov.asd.tac.constellation.utilities.glyphs; + ///* // * Copyright 2010-2020 Australian Signals Directorate // * diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphRectangleBuffer.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphRectangleBuffer.java similarity index 97% rename from CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphRectangleBuffer.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphRectangleBuffer.java index 82d221fddf..c65dc6fb4b 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphRectangleBuffer.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphRectangleBuffer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs; +package au.gov.asd.tac.constellation.utilities.glyphs; import java.awt.Color; import java.awt.Graphics2D; @@ -39,7 +39,7 @@ * * @author algol */ -final class GlyphRectangleBuffer { +public final class GlyphRectangleBuffer { // The buffers that glyphs will be drawn to. // Each one of these will eventually be copied to a texture buffer. @@ -108,7 +108,7 @@ final class GlyphRectangleBuffer { * only need grayscale). Use BufferedImage.TYPE_INT_ARGB for standalone to * see the pretty colors. */ - GlyphRectangleBuffer(final int width, final int height, final int bufferType) { + public GlyphRectangleBuffer(final int width, final int height, final int bufferType) { this.width = width; this.height = height; this.bufferType = bufferType; @@ -247,7 +247,7 @@ public int hashCode() { * only record the size-extra. * @return rectIndex The index of the rectangle in this.memory. */ - int addRectImage(final BufferedImage img, final int extra) { + public int addRectImage(final BufferedImage img, final int extra) { final int w = img.getWidth(); final int h = img.getHeight(); diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphStreamContext.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphStreamContext.java similarity index 94% rename from CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphStreamContext.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphStreamContext.java index c3055fceb0..6194d6a163 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphStreamContext.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphStreamContext.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs; +package au.gov.asd.tac.constellation.utilities.glyphs; /** * Contains basic information required in every GlyphStream implementation. diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphsFrame.form b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphsFrame.form similarity index 98% rename from CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphsFrame.form rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphsFrame.form index ff03e3c739..bfc633551a 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphsFrame.form +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphsFrame.form @@ -5,9 +5,6 @@ - - - diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphsFrame.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphsFrame.java similarity index 98% rename from CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphsFrame.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphsFrame.java index 39d672b04d..0b19aec712 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/GlyphsFrame.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/GlyphsFrame.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs; +package au.gov.asd.tac.constellation.utilities.glyphs; -import au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs.FontInfo.ParsedFontInfo; +import au.gov.asd.tac.constellation.utilities.glyphs.FontInfo.ParsedFontInfo; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; @@ -144,7 +144,6 @@ private void initComponents() { setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); setTitle("Glyph rendering example"); setLocationByPlatform(true); - setPreferredSize(new java.awt.Dimension(1200, 400)); textLines.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); textLines.addActionListener(new java.awt.event.ActionListener() { diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelFontsPreferenceKeys.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/LabelFontsPreferenceKeys.java similarity index 87% rename from CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelFontsPreferenceKeys.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/LabelFontsPreferenceKeys.java index c273a4aef0..209c2f33cd 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelFontsPreferenceKeys.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/LabelFontsPreferenceKeys.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.visual.opengl.utilities; +package au.gov.asd.tac.constellation.utilities.glyphs; import au.gov.asd.tac.constellation.utilities.text.SeparatorConstants; -import au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs.ConstellationLabelFonts; -import au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs.FontInfo; -import au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs.GlyphManagerBI; +import au.gov.asd.tac.constellation.utilities.glyphs.ConstellationLabelFonts; +import au.gov.asd.tac.constellation.utilities.glyphs.FontInfo; +import au.gov.asd.tac.constellation.utilities.glyphs.GlyphManagerBI; import java.util.prefs.Preferences; import org.openide.util.NbPreferences; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/NodeGlyphStreamContext.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/NodeGlyphStreamContext.java similarity index 89% rename from CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/NodeGlyphStreamContext.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/NodeGlyphStreamContext.java index ffee9037ea..0f0fe1f895 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/glyphs/NodeGlyphStreamContext.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/glyphs/NodeGlyphStreamContext.java @@ -13,7 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.visual.opengl.utilities.glyphs; +package au.gov.asd.tac.constellation.utilities.glyphs; + +import au.gov.asd.tac.constellation.utilities.glyphs.GlyphStreamContext; /** * Object that contains all the contextual information needed by diff --git a/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Matrix44f.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Matrix44f.java index 3c3e168eaa..2205faf538 100644 --- a/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Matrix44f.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Matrix44f.java @@ -32,6 +32,7 @@ public final class Matrix44f { public final float[] a; public static final int LENGTH = 16; + public static final int BYTES = LENGTH * Float.BYTES; private static final float[] IDENTITY44F = { diff --git a/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector2f.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector2f.java index 1407840aea..58efc90b3f 100644 --- a/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector2f.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector2f.java @@ -35,6 +35,11 @@ public final class Vector2f { public Vector2f() { a = new float[LENGTH]; } + + public Vector2f(final float x, final float y) { + a = new float[LENGTH]; + set(x, y); + } public void set(final float x, final float y) { a[0] = x; @@ -45,6 +50,22 @@ public void set(final Vector2f v) { a[0] = v.a[0]; a[1] = v.a[1]; } + + public float getX() { + return a[0]; + } + + public void setX(final float x) { + a[0] = x; + } + + public float getY() { + return a[1]; + } + + public void setY(final float y) { + a[1] = y; + } public void scale(final float s) { a[0] *= s; diff --git a/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector2i.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector2i.java new file mode 100644 index 0000000000..0a30785d61 --- /dev/null +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector2i.java @@ -0,0 +1,82 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.utilities.graphics; + +public class Vector2i { + /** + * The length of a vector. + */ + public static final int LENGTH = 2; + + /** + * The contents of this vector. + */ + public final int[] a; + + public Vector2i() { + a = new int[LENGTH]; + } + + public Vector2i(final int x, final int y) { + a = new int[LENGTH]; + set(x, y); + } + + public void set(final int x, final int y) { + a[0] = x; + a[1] = y; + } + + public void set(final Vector2i v) { + a[0] = v.a[0]; + a[1] = v.a[1]; + } + + public int getX() { + return a[0]; + } + + public void setX(final int x) { + a[0] = x; + } + + public int getY() { + return a[1]; + } + + public void setY(final int y) { + a[1] = y; + } + + public static void add(final Vector2i result, final Vector2i a, final Vector2i b) { + result.a[0] = a.a[0] + b.a[0]; + result.a[1] = a.a[1] + b.a[1]; + } + + public static void subtract(final Vector2i result, final Vector2i a, final Vector2i b) { + result.a[0] = a.a[0] - b.a[0]; + result.a[1] = a.a[1] - b.a[1]; + } + + public static Vector2i[] createArray(final int length) { + Vector2i[] array = new Vector2i[length]; + for (int i = 0; i < length; i++) { + array[i] = new Vector2i(); + } + + return array; + } +} diff --git a/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector3f.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector3f.java index 2d7e2d1c15..dfc5c81d24 100644 --- a/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector3f.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector3f.java @@ -28,6 +28,11 @@ public final class Vector3f implements Serializable { * The length of a vector. */ public static final int LENGTH = 3; + + /** + * Size in bytes of a vector + */ + public static final int BYTES = LENGTH * Float.BYTES; /** * The contents of this vector. @@ -364,14 +369,26 @@ public void setZ(final float z) { public float getR() { return a[0]; } + + public void setR(final float r) { + a[0] = r; + } public float getG() { return a[1]; } + + public void setG(final float g) { + a[1] = g; + } public float getB() { return a[2]; } + + public void setB(final float b) { + a[2] = b; + } /** * Does this vector contain valid numbers. diff --git a/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector3i.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector3i.java new file mode 100644 index 0000000000..d41f1902a1 --- /dev/null +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector3i.java @@ -0,0 +1,208 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.utilities.graphics; + +import java.io.Serializable; + +/** + * A vector of three integer point values. + */ +public final class Vector3i implements Serializable { + + /** + * The length of a vector. + */ + public static final int LENGTH = 3; + + /** + * The contents of this vector. + */ + public final int[] a; + + /** + * Create a new zero vector. + */ + public Vector3i() { + a = new int[LENGTH]; + } + + /** + * Create a new vector with assigned values. + * + * @param x X. + * @param y Y. + * @param z Z. + */ + public Vector3i(final int x, final int y, final int z) { + a = new int[LENGTH]; + set(x, y, z); + } + + /** + * Create a new vector using values from an existing vector. + * + * @param v An existing vector. + */ + public Vector3i(final Vector3i v) { + a = new int[LENGTH]; + a[0] = v.a[0]; + a[1] = v.a[1]; + a[2] = v.a[2]; + } + + /** + * Add another vector to this vector. + * + * @param v The Vector3i to add to this. + */ + public void add(final Vector3i v) { + a[0] += v.a[0]; + a[1] += v.a[1]; + a[2] += v.a[2]; + } + + /** + * Add two vectors, store the result in this vector. + * + * @param u An existing Vector3i. + * @param v Another existing Vector3i. + * @return the sum of the 2 given vectors. + */ + public static Vector3i add(final Vector3i u, final Vector3i v) { + return new Vector3i(u.a[0] + v.a[0], u.a[1] + v.a[1], u.a[2] + v.a[2]); + } + + /** + * Set the values of this vector from x,y,z. + * + * @param x X. + * @param y Y. + * @param z Z. + */ + public void set(final int x, final int y, final int z) { + a[0] = x; + a[1] = y; + a[2] = z; + } + + /** + * Set the values of this vector from an existing vector. + * + * @param v An existing Vector3f. + */ + public void set(final Vector3i v) { + a[0] = v.a[0]; + a[1] = v.a[1]; + a[2] = v.a[2]; + } + + /** + * Subtract another vector from this vector. + * + * @param v An existing vector. + */ + public void subtract(final Vector3i v) { + a[0] -= v.a[0]; + a[1] -= v.a[1]; + a[2] -= v.a[2]; + } + + /** + * Calculate u-v, store the result in a new vector. + * + * @param u An existing vector. + * @param v Another existing vector. + * @return the difference between the 2 given vectors. + */ + public static Vector3i subtract(final Vector3i u, final Vector3i v) { + return new Vector3i(u.a[0] - v.a[0], u.a[1] - v.a[1], u.a[2] - v.a[2]); + } + + /** + * Calculate u-v, store the result in result. + * + * @param result The result. + * @param u An existing vector. + * @param v Another existing vector. + */ + public static void subtract(final Vector3i result, final Vector3i u, final Vector3i v) { + result.a[0] = u.a[0] - v.a[0]; + result.a[1] = u.a[1] - v.a[1]; + result.a[2] = u.a[2] - v.a[2]; + } + + /** + * Test for the elements of this Vector3i as all zero. + * + * @return True if all elements of this Vector3i are zero, false otherwise. + */ + public boolean isZero() { + return a[0] == 0 && a[1] == 0 && a[2] == 0; + } + + public int getX() { + return a[0]; + } + + public void setX(final int x) { + a[0] = x; + } + + public int getY() { + return a[1]; + } + + public void setY(final int y) { + a[1] = y; + } + + public int getZ() { + return a[2]; + } + + public void setZ(final int z) { + a[2] = z; + } + + public int getR() { + return a[0]; + } + + public int getG() { + return a[1]; + } + + public int getB() { + return a[2]; + } + + public int getU() { + return a[0]; + } + + public int getV() { + return a[1]; + } + + public int getW() { + return a[2]; + } + + @Override + public String toString() { + return String.format("3d[%d,%d,%d]", a[0], a[1], a[2]); + } +} diff --git a/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector4f.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector4f.java index 9c8e3ee8bf..f731877726 100644 --- a/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector4f.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector4f.java @@ -26,6 +26,11 @@ public final class Vector4f { * The length of a vector. */ public static final int LENGTH = 4; + + /** + * Size in bytes of a vector + */ + public static final int BYTES = LENGTH * Float.BYTES; /** * The contents of this vector. diff --git a/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector4i.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector4i.java new file mode 100644 index 0000000000..ef3492081d --- /dev/null +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/graphics/Vector4i.java @@ -0,0 +1,179 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.utilities.graphics; + +/** + * A vector of four integer point values. + */ +public final class Vector4i { + + /** + * The length of a vector. + */ + public static final int LENGTH = 4; + + /** + * Size in bytes of a vector + */ + public static final int BYTES = LENGTH * Integer.BYTES; + + /** + * The contents of this vector. + */ + public final int[] a; + + /** + * Create a new zero vector. + */ + public Vector4i() { + a = new int[LENGTH]; + } + + /** + * Create a new vector with assigned values. + * + * @param x the x component of the vector. + * @param y the y component of the vector. + * @param z the z component of the vector. + * @param w the w component of the vector. + */ + public Vector4i(final int x, final int y, final int z, int w) { + a = new int[LENGTH]; + set(x, y, z, w); + } + + /** + * Set the value of this vector from x,y,z,w. + * + * @param x the x component of the vector. + * @param y the y component of the vector. + * @param z the z component of the vector. + * @param w the w component of the vector. + */ + public void set(final int x, final int y, final int z, final int w) { + a[0] = x; + a[1] = y; + a[2] = z; + a[3] = w; + } + + /** + * Set the values of this vector from an existing vector. + * + * @param v the vector to be copied. + */ + public void set(final Vector4i v) { + a[0] = v.a[0]; + a[1] = v.a[1]; + a[2] = v.a[2]; + a[3] = v.a[3]; + } + + public static void add(final Vector4i result, final Vector4i a, final Vector4i b) { + result.a[0] = a.a[0] + b.a[0]; + result.a[1] = a.a[1] + b.a[1]; + result.a[2] = a.a[2] + b.a[2]; + result.a[3] = a.a[3] + b.a[3]; + } + + public static void subtract(final Vector4i result, final Vector4i a, final Vector4i b) { + result.a[0] = a.a[0] - b.a[0]; + result.a[1] = a.a[1] - b.a[1]; + result.a[2] = a.a[2] - b.a[2]; + result.a[3] = a.a[3] - b.a[3]; + } + + public Vector3i toVector3i() { + return new Vector3i(a[0], a[1], a[2]); + } + + /** + * Return the x component. + * + * @return The x component. + */ + public int getX() { + return a[0]; + } + + /** + * Return the y component. + * + * @return The y component. + */ + public int getY() { + return a[1]; + } + + /** + * Return the z component. + * + * @return The z component. + */ + public int getZ() { + return a[2]; + } + + /** + * Return the w component. + * + * @return The w component. + */ + public int getW() { + return a[3]; + } + + /** + * Return the red component. + * + * @return The red component. + */ + public int getR() { + return a[0]; + } + + /** + * Return the green component. + * + * @return The green component. + */ + public int getG() { + return a[1]; + } + + /** + * Return the blue component. + * + * @return The blue component. + */ + public int getB() { + return a[2]; + } + + /** + * Return the alpha component. + * + * @return The alpha component. + */ + public int getA() { + return a[3]; + } + + @Override + public String toString() { + return String.format("4d[%d,%d,%d,%d]", a[0], a[1], a[2], a[3]); + } +} diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelUtilities.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/text/LabelUtilities.java similarity index 98% rename from CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelUtilities.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/text/LabelUtilities.java index a05279f84e..d50bedf1c3 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/utilities/LabelUtilities.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/text/LabelUtilities.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.visual.opengl.utilities; +package au.gov.asd.tac.constellation.utilities.text; import au.gov.asd.tac.constellation.utilities.color.ConstellationColor; import java.util.ArrayList; diff --git a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/DirectionIndicatorsAction.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/DirectionIndicatorsAction.java similarity index 95% rename from CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/DirectionIndicatorsAction.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/DirectionIndicatorsAction.java index 161f432910..e0b265d450 100644 --- a/CoreOpenGLDisplay/src/au/gov/asd/tac/constellation/visual/opengl/renderer/DirectionIndicatorsAction.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/DirectionIndicatorsAction.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.visual.opengl.renderer; +package au.gov.asd.tac.constellation.utilities.visual; import java.awt.event.ActionEvent; import java.util.concurrent.atomic.AtomicBoolean; @@ -27,7 +27,7 @@ import org.openide.util.NbBundle.Messages; import org.openide.util.actions.Presenter; -@ActionID(category = "Display", id = "au.gov.asd.tac.constellation.visual.opengl.renderer.DirectionIndicatorsAction") +@ActionID(category = "Display", id = "au.gov.asd.tac.constellation.utilities.visual.DirectionIndicatorsAction") @ActionRegistration(displayName = "#CTL_DirectionIndicatorsAction", surviveFocusChange = true, lazy = false) @ActionReference(path = "Menu/Display", position = 1200) @Messages("CTL_DirectionIndicatorsAction=Direction Indicators") diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/NewLineModel.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/NewLineModel.java similarity index 97% rename from CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/NewLineModel.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/NewLineModel.java index 0fbb29e901..03dbf9c58b 100644 --- a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/NewLineModel.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/NewLineModel.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.graph.interaction.visual.renderables; +package au.gov.asd.tac.constellation.utilities.visual; import au.gov.asd.tac.constellation.utilities.camera.Camera; import au.gov.asd.tac.constellation.utilities.graphics.Vector3f; diff --git a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/SelectionBoxModel.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/SelectionBoxModel.java similarity index 97% rename from CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/SelectionBoxModel.java rename to CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/SelectionBoxModel.java index 0e7c075dc2..40c4a01ff6 100644 --- a/CoreInteractiveGraph/src/au/gov/asd/tac/constellation/graph/interaction/visual/renderables/SelectionBoxModel.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/SelectionBoxModel.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package au.gov.asd.tac.constellation.graph.interaction.visual.renderables; +package au.gov.asd.tac.constellation.utilities.visual; import java.awt.Point; diff --git a/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/VisualChange.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/VisualChange.java index 922f4c28c1..9d35c7e1ad 100644 --- a/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/VisualChange.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/VisualChange.java @@ -119,4 +119,45 @@ public int hashCode() { public int compareTo(VisualChange o) { return o == null ? -1 : id == o.id ? 0 : Integer.compare(order, o.order); } + + /** + * Returns either an int[2] of min/max, or null if this change is empty. + * Element 0 returned is the lowest vertex index in changeList. + * Element 1 returned is the highest vertex index in changeList. + * + * If changeList has a size of 1 both element 0 and 1 will have that value. + * + * @return either an int[2] of min/max, or null if this change is empty + */ + public int[] getRange() { + if (changeList != null && changeListSize > 0) { + int minMax[] = new int[2]; + minMax[0] = minMax[1] = changeList[0]; + for (int i = 1; i < changeListSize; ++i) { + final int index = changeList[i]; + if (index < minMax[0]) { + minMax[0] = index; + } else if (index > minMax[1]) { + minMax[1] = index; + } + } + return minMax; + } else { + return null; + } + } + + /** + * It's unclear from the class javadoc what a null changeList means. Does + * it mean for a given VisualAccess we should vertices 0 to changeListSize + * have changed, but vertices after changeListSize have not? + * + * For safety calling code can use this function, then ignore the change + * and process all of the vertices in the VisualAccess. + * + * @return + */ + public boolean isEmpty() { + return changeList == null; + } } diff --git a/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/VisualManager.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/VisualManager.java index 807b6dac11..5715c638dc 100644 --- a/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/VisualManager.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/VisualManager.java @@ -71,6 +71,11 @@ public final class VisualManager { private boolean rendererIdle = true; private boolean indigenousChanges = false; private boolean refreshProcessor = false; + private boolean shouldRedraw = false; + private static boolean continuousRedrawing = false; //TODO: change this after testing + + public static void ToggleContinuousRedraw() { continuousRedrawing = !continuousRedrawing; } + public static boolean ContinuoslyRedraw() { return continuousRedrawing; } /** * Construct a VisualManager to delegate between the supplied @@ -107,6 +112,7 @@ public void destroy() { processor.destroyCanvas(); } + @SuppressWarnings("deprecation") @Override public void finalize() throws Throwable { try { @@ -127,6 +133,10 @@ public final void startProcessing() { while (isProcessing) { final NavigableSet changes = new TreeSet<>(); while (true) { + // Continuous rendering is not necessary but useful for debugging the rendering code + if (continuousRedrawing) { + shouldRedraw = true; + } try { final VisualOperation operation = operationQueue.take(); if (operation == SIGNIFY_PROCESSOR_IDLE_OPERATION) { @@ -135,11 +145,13 @@ public final void startProcessing() { indigenousChanges = true; } else if (operation == REFRESH_PROCESSOR_OPERATION) { refreshProcessor = true; + } else if (operation == REQUEST_REDRAW) { + shouldRedraw = true; } else { operation.apply(); } changes.addAll(operation.getVisualChanges()); - if (rendererIdle && !changes.isEmpty()) { + if (rendererIdle && (!changes.isEmpty() || shouldRedraw)) { rendererIdle = false; break; } @@ -152,6 +164,7 @@ public final void startProcessing() { } if (isProcessing) { // this call blocks until the updating is done + shouldRedraw = false; processor.update(changes, access, indigenousChanges, refreshProcessor); indigenousChanges = false; refreshProcessor = false; @@ -296,10 +309,27 @@ public final void addSingleChangeOperation(final VisualChange change) { public final void addMultiChangeOperation(final List changes) { addOperation(constructMultiChangeOperation(changes)); } + + void requestRedraw() { + addOperation(REQUEST_REDRAW); + } void signifyProcessorIdle() { addOperation(SIGNIFY_PROCESSOR_IDLE_OPERATION); - } + } + + private final VisualOperation REQUEST_REDRAW = new VisualOperation() { + + @Override + public int getPriority() { + return VisualPriority.REFRESH_PRIORITY.getValue(); + } + + @Override + public List getVisualChanges() { + return Collections.emptyList(); + } + }; private final VisualOperation SIGNIFY_PROCESSOR_IDLE_OPERATION = new VisualOperation() { diff --git a/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/VisualProcessor.java b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/VisualProcessor.java index 094b207a16..40b14239e1 100644 --- a/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/VisualProcessor.java +++ b/CoreUtilities/src/au/gov/asd/tac/constellation/utilities/visual/VisualProcessor.java @@ -18,6 +18,8 @@ import java.awt.Component; import java.awt.image.BufferedImage; import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -27,6 +29,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.Semaphore; +import java.util.logging.Logger; /** * A VisualProcessor is responsible for providing a visualisation of an object @@ -65,8 +68,10 @@ public abstract class VisualProcessor { protected boolean isInitialised = false; - private VisualManager manager; + protected VisualManager manager; private final Semaphore updateOccuring = new Semaphore(0); + + private static final Logger LOGGER = Logger.getLogger(VisualProcessor.class.getName()); /** * Get the {@link Component} that this processor is using for its @@ -167,6 +172,19 @@ public final void stopVisualising() { protected void signalProcessorIdle() { manager.signifyProcessorIdle(); } + + /** + * Request the manager redraw us (visualupdate). This is useful if we have + * resized the canvas as the Vulkan display does this in two passes, the first + * recreates swapchain resources without rendering, the second, which occurs + * thanks to this request, redraws the canvas with the new resources. + * This can also be used for continuous rendering if we want to test the frame + * rate or ever implement any non-event driven rendering updates (eg some + * in-graph animation). + */ + protected void requestRedraw() { + manager.requestRedraw(); + } /** * Informs the manager that this processor needs to recompute all visual @@ -204,6 +222,14 @@ final void update(final Collection changes, final VisualAccess acc changes.addAll(getFullRefreshSet(access)); } processChangeSet(changes, access); + } catch (Exception e) { + final StringWriter exceptionTraceWriter = new StringWriter(); + final PrintWriter exceptionPrintWriter = new PrintWriter(exceptionTraceWriter); + e.printStackTrace(exceptionPrintWriter); + exceptionPrintWriter.flush(); + final String exceptionMessage = exceptionTraceWriter.toString(); + LOGGER.severe(String.format("Exception processing visual changes:\r\n%s", exceptionMessage)); + throw e; } finally { access.endUpdate(); } diff --git a/CoreVulkanDisplay/build.xml b/CoreVulkanDisplay/build.xml new file mode 100644 index 0000000000..2af6fc88c1 --- /dev/null +++ b/CoreVulkanDisplay/build.xml @@ -0,0 +1,8 @@ + + + + + + Builds, tests, and runs the project au.gov.asd.tac.constellation.visual.vulkan. + + diff --git a/CoreVulkanDisplay/manifest.mf b/CoreVulkanDisplay/manifest.mf new file mode 100644 index 0000000000..3d69d8162f --- /dev/null +++ b/CoreVulkanDisplay/manifest.mf @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +AutoUpdate-Show-In-Client: true +OpenIDE-Module: au.gov.asd.tac.constellation.visual.vulkan +OpenIDE-Module-Localizing-Bundle: au/gov/asd/tac/constellation/visual/vulkan/Bundle.properties +OpenIDE-Module-Specification-Version: 1.0 + diff --git a/CoreVulkanDisplay/nbproject/build-impl.xml b/CoreVulkanDisplay/nbproject/build-impl.xml new file mode 100644 index 0000000000..2db13555ad --- /dev/null +++ b/CoreVulkanDisplay/nbproject/build-impl.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + You must set 'suite.dir' to point to your containing module suite + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CoreVulkanDisplay/nbproject/genfiles.properties b/CoreVulkanDisplay/nbproject/genfiles.properties new file mode 100644 index 0000000000..517b070ed3 --- /dev/null +++ b/CoreVulkanDisplay/nbproject/genfiles.properties @@ -0,0 +1,8 @@ +build.xml.data.CRC32=2a87fbba +build.xml.script.CRC32=ad58b29b +build.xml.stylesheet.CRC32=15ca8a54@2.80 +# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. +# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. +nbproject/build-impl.xml.data.CRC32=2a87fbba +nbproject/build-impl.xml.script.CRC32=a989f11d +nbproject/build-impl.xml.stylesheet.CRC32=49aa68b0@2.80 diff --git a/CoreVulkanDisplay/nbproject/project.properties b/CoreVulkanDisplay/nbproject/project.properties new file mode 100644 index 0000000000..2484b83bfa --- /dev/null +++ b/CoreVulkanDisplay/nbproject/project.properties @@ -0,0 +1,4 @@ +javac.source=11 +javac.target=11 +javac.compilerargs=-Xlint -Xlint:-serial +nbm.needs.restart=true \ No newline at end of file diff --git a/CoreVulkanDisplay/nbproject/project.xml b/CoreVulkanDisplay/nbproject/project.xml new file mode 100644 index 0000000000..350e2d1e7c --- /dev/null +++ b/CoreVulkanDisplay/nbproject/project.xml @@ -0,0 +1,66 @@ + + + org.netbeans.modules.apisupport.project + + + au.gov.asd.tac.constellation.visual.vulkan + + + + au.gov.asd.tac.constellation.dependencies + + + + 1.0 + + + + au.gov.asd.tac.constellation.graph + + + + 1.0.6 + + + + au.gov.asd.tac.constellation.graph.visual + + + + 1.0 + + + + au.gov.asd.tac.constellation.utilities + + + + 1.0 + + + + org.openide.modules + + + + 7.56 + + + + org.openide.util.ui + + + + 9.16 + + + + + au.gov.asd.tac.constellation.visual.vulkan + au.gov.asd.tac.constellation.visual.vulkan.renderables + au.gov.asd.tac.constellation.visual.vulkan.resourcetypes + au.gov.asd.tac.constellation.visual.vulkan.utils + + + + diff --git a/CoreVulkanDisplay/nbproject/suite.properties b/CoreVulkanDisplay/nbproject/suite.properties new file mode 100644 index 0000000000..29d7cc9bd6 --- /dev/null +++ b/CoreVulkanDisplay/nbproject/suite.properties @@ -0,0 +1 @@ +suite.dir=${basedir}/.. diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/Bundle.properties b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/Bundle.properties new file mode 100644 index 0000000000..0c3d248b3b --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/Bundle.properties @@ -0,0 +1,4 @@ +OpenIDE-Module-Display-Category=Core +OpenIDE-Module-Long-Description=The Vulkan graph renderer. +OpenIDE-Module-Name=Core Display Vulkan +OpenIDE-Module-Short-Description=Core Vulkan Display \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKCanvas.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKCanvas.java new file mode 100644 index 0000000000..4d06349171 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKCanvas.java @@ -0,0 +1,326 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package au.gov.asd.tac.constellation.visual.vulkan; + +import au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKGraphLogger; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKMissingEnums; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssert; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssertNotNull; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.UINT32_MAX; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkFailed; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkSucceeded; +import com.google.common.primitives.Ints; +import java.nio.IntBuffer; +import java.util.logging.Level; +import org.lwjgl.vulkan.awt.AWTVKCanvas; +import javax.swing.SwingUtilities; +import org.lwjgl.system.MemoryStack; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.vulkan.KHRSurface.vkDestroySurfaceKHR; +import static org.lwjgl.vulkan.KHRSurface.vkGetPhysicalDeviceSurfaceFormatsKHR; +import static org.lwjgl.vulkan.KHRSurface.vkGetPhysicalDeviceSurfaceCapabilitiesKHR; +import static org.lwjgl.vulkan.KHRSurface.vkGetPhysicalDeviceSurfacePresentModesKHR; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import org.lwjgl.vulkan.VkExtent2D; +import org.lwjgl.vulkan.VkSurfaceCapabilitiesKHR; +import org.lwjgl.vulkan.VkSurfaceFormatKHR; +import org.lwjgl.vulkan.awt.VKData; + + +public class CVKCanvas extends AWTVKCanvas { + private final CVKVisualProcessor cvkVisualProcessor; + private final CVKRenderer cvkRenderer; + private VkSurfaceCapabilitiesKHR vkSurfaceCapabilities = null; + private CVKMissingEnums.VkFormat selectedFormat = CVKMissingEnums.VkFormat.VK_FORMAT_NONE; + private CVKMissingEnums.VkColorSpaceKHR selectedColourSpace = CVKMissingEnums.VkColorSpaceKHR.VK_COLOR_SPACE_NONE; + private CVKMissingEnums.VkPresentModeKHR selectedPresentationMode = CVKMissingEnums.VkPresentModeKHR.VK_PRESENT_MODE_NONE; + private final VkExtent2D vkCurrentSurfaceExtent = VkExtent2D.malloc().set(0, 0); + private int frameNumber = 0; + + + // ========================> Getters <======================== \\ + + public int GetFrameNumber() { return frameNumber; } + public CVKRenderer GetRenderer() { return cvkRenderer; } + public CVKGraphLogger GetLogger() { return cvkVisualProcessor.GetLogger(); } + public void VerifyInRenderThread() { cvkVisualProcessor.VerifyInRenderThread(); } + public boolean IsRenderThreadCurrent() { return cvkVisualProcessor.IsRenderThreadCurrent(); } + public boolean IsRenderThreadAlive() { return cvkVisualProcessor.IsRenderThreadAlive(); } + public void AddRenderable(CVKRenderable renderable) { cvkRenderer.AddRenderable(renderable); } + public long GetSurfaceHandle() { return surface; } + public VkExtent2D GetCurrentSurfaceExtent() { return vkCurrentSurfaceExtent; } + public VkSurfaceCapabilitiesKHR GetSurfaceCapabilities() { return vkSurfaceCapabilities; } + public CVKMissingEnums.VkFormat GetSurfaceFormat() { return selectedFormat; } + public CVKMissingEnums.VkColorSpaceKHR GetSurfaceColourSpace() { return selectedColourSpace; } + public CVKMissingEnums.VkPresentModeKHR GetPresentationMode() { return selectedPresentationMode; } + + + + // ========================> Lifetime <======================== \\ + + private static VKData NewVKData() { + VKData vkData = new VKData(); + vkData.instance = CVKInstance.GetVkInstance(); + return vkData; + } + + /** + * CVKCanvas belongs to a JPanel which in turn belongs to a tabbed control. + * When we are constructed as part of the VisualGraphOpener call chain that + * panel hasn't yet been added to it's parent. In that state we cannot lock + * the cvkCanvas surface (JAWT_DrawingSurface_Lock returns an error). Without + * the surface we cannot initialise all the Vulkan resources we need. + * + * Our parent class AWTCanvas will call initVK when we have a valid surface to + * which to render. Until that time we cannot do any device initialisation or + * rendering. CVKRenderer is created and can receive renderables which in + * turn can process user input events or graph load events, but we never pump + * the render loop so no device dependent actions will occur until our surface + * is ready. + * + * @param visualProcessor: the visual processor that created this canvas. It + * holds a logger that is specific to graph this canvas displays. It also tracks + * the rendering thread (on Windows there is a single AWT event thread that + * processes the rendering for all graphs but this may not be true for all + * platforms). + */ + public CVKCanvas(CVKVisualProcessor visualProcessor) { + super(NewVKData()); + CVKAssertNotNull(visualProcessor); + vkSurfaceCapabilities = VkSurfaceCapabilitiesKHR.malloc(); + this.cvkVisualProcessor = visualProcessor; + cvkRenderer = new CVKRenderer(this, visualProcessor); + } + + public void Destroy() { + try { + GetLogger().StartLogSection("CVKCanvas Destroy"); + cvkRenderer.Destroy(); + } finally { + GetLogger().EndLogSection("CVKCanvas Destroy"); + } + + if (vkSurfaceCapabilities != null) + { + vkSurfaceCapabilities.free(); + vkSurfaceCapabilities = null; + } + } + + + // ========================> Surface <======================== \\ + + /** + * Updates surface capabilities which may have changed due to our canvas + * being resized. It also updates the ideal extent which is either the + * capabilities currentExtent capped to minImageExtent and maxImageExtent + * + * @return error code + */ + public int UpdateSurfaceCapabilities() { + int ret; + try { + GetLogger().StartLogSection("Canvas updating surface capalities"); + + ret = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(CVKDevice.GetVkPhysicalDevice(), GetSurfaceHandle(), vkSurfaceCapabilities); + if (VkSucceeded(ret)) { + vkCurrentSurfaceExtent.set(vkSurfaceCapabilities.currentExtent()); + if (vkCurrentSurfaceExtent.width() == UINT32_MAX) { + //TODO: find out how big our surface is somehow + vkCurrentSurfaceExtent.set(800, 600); + GetLogger().log(Level.WARNING, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR returned extent with the magic don't care size"); + } + + // TODO: clean this up once the attribute calculator bug is fixed + int width = vkCurrentSurfaceExtent.width(); + int height = vkCurrentSurfaceExtent.height(); + + final int minSurfaceWidth = vkSurfaceCapabilities.minImageExtent().width(); + final int minSurfaceHeight = vkSurfaceCapabilities.minImageExtent().height(); + + final VkExtent2D maxFrameBufferExtent = CVKDevice.GetMaxFrameBufferExtent(); + GetLogger().info("Calculating surface height:\n\tvkCurrentSurfaceExtent:%d\n\tvkSurfaceCapabilities.min:%d\n\tvkSurfaceCapabilities.max:%d\n\tvkMaxFramebufferExtent:%d", + height, minSurfaceHeight, vkSurfaceCapabilities.maxImageExtent().height(), maxFrameBufferExtent.height()); + + CVKAssert(width >= minSurfaceWidth); + CVKAssert(height >= minSurfaceHeight); + + // Constrain to dimensions supported by the current surface capabilities + width = Ints.constrainToRange(width, minSurfaceWidth, vkSurfaceCapabilities.maxImageExtent().width()); + height = Ints.constrainToRange(height, minSurfaceHeight, vkSurfaceCapabilities.maxImageExtent().height()); + + // Constrain to framebuffer maximums + width = Ints.constrainToRange(width, 0, maxFrameBufferExtent.width()); + height = Ints.constrainToRange(height, 0, maxFrameBufferExtent.height()); + + vkCurrentSurfaceExtent.set(width, height); + GetLogger().info("Ideal extent will be %d x %d", vkCurrentSurfaceExtent.width(), vkCurrentSurfaceExtent.height()); + } + else { + GetLogger().severe("vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed with error: %d (0x%08X)", ret, ret); + } + + // Figure out our ideal backbuffer size + // The current size of the surface will either be explicit, which we use, or + // set to a value indicating it will use whatever is set in the swap chain. + GetLogger().info(String.format("Surface will be %dx%d", vkCurrentSurfaceExtent.width(), vkCurrentSurfaceExtent.height())); + } finally { + GetLogger().EndLogSection("Canvas updating surface capalities"); + } + + return ret; + } + + private int SelectPresentationMode() { + int ret; + try (MemoryStack stack = stackPush()) { + // Surface formats our device can use + IntBuffer pInt = stack.mallocInt(1); + pInt.put(0, 0); + ret = vkGetPhysicalDeviceSurfaceFormatsKHR(CVKDevice.GetVkPhysicalDevice(), GetSurfaceHandle(), pInt, null); + if (VkFailed(ret)) return ret; + int numFormats = pInt.get(0); + if (numFormats > 0) { + VkSurfaceFormatKHR.Buffer vkSurfaceFormats = VkSurfaceFormatKHR.callocStack(numFormats, stack); + ret = vkGetPhysicalDeviceSurfaceFormatsKHR(CVKDevice.GetVkPhysicalDevice(), surface, pInt, vkSurfaceFormats); + if (VkFailed(ret)) return ret; + + GetLogger().info(String.format("Available surface formats:")); + for (int i = 0; i < numFormats; ++i) { + VkSurfaceFormatKHR surfaceFormat = vkSurfaceFormats.get(i); + CVKMissingEnums.VkColorSpaceKHR colorSpace = CVKMissingEnums.VkColorSpaceKHR.GetByValue(surfaceFormat.colorSpace()); + CVKMissingEnums.VkFormat format = CVKMissingEnums.VkFormat.values()[surfaceFormat.format()]; + + // We want to use VK_FORMAT_B8G8R8A8_SRGB for the surface format. That's a byte for each + // of RGBA so it's easy to work with but where the value is nonlinearly mapped to + // intensity. Check out sRGB, but in short given the nature of human vision and the + // display characteristics of most displays we are better off concentrating granularity + // around intensities we can differentiate rather than just using a linear mapping. + if (format == CVKMissingEnums.VkFormat.VK_FORMAT_B8G8R8A8_SRGB) { + selectedFormat = format; + } + + // For the reason above we want the sRGB colour space + if (colorSpace == CVKMissingEnums.VkColorSpaceKHR.VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { + selectedColourSpace = colorSpace; + } + + GetLogger().info(String.format(" %s:%s", format.name(), colorSpace.name())); + } + } + + if (selectedFormat == CVKMissingEnums.VkFormat.VK_FORMAT_NONE) { + throw new RuntimeException("Required surface format unsupported (VK_FORMAT_B8G8R8A8_SRGB)"); + } + if (selectedColourSpace == CVKMissingEnums.VkColorSpaceKHR.VK_COLOR_SPACE_NONE) { + throw new RuntimeException("Required color space unsupported (VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)"); + } + + + // Presentation modes our device can use for our surface + pInt.put(0, 0); + ret = vkGetPhysicalDeviceSurfacePresentModesKHR(CVKDevice.GetVkPhysicalDevice(), GetSurfaceHandle(), pInt, null); + if (VkFailed(ret)) return ret; + int numPresentationModes = pInt.get(0); + if (numPresentationModes > 0) { + IntBuffer presentationModes = stack.mallocInt(numPresentationModes); + vkGetPhysicalDeviceSurfacePresentModesKHR(CVKDevice.GetVkPhysicalDevice(), GetSurfaceHandle(), pInt, presentationModes); + + GetLogger().info(String.format("Supported presentation modes:")); + for (int i = 0; i < numPresentationModes; ++i) { + CVKMissingEnums.VkPresentModeKHR presentationMode = CVKMissingEnums.VkPresentModeKHR.values()[presentationModes.get(i)]; + // Mailbox is our first choice + if (presentationMode == CVKMissingEnums.VkPresentModeKHR.VK_PRESENT_MODE_MAILBOX_KHR) { + selectedPresentationMode = presentationMode; + } + // Second preference is VK_PRESENT_MODE_FIFO_KHR, selected unless we already have mailbox + else if (presentationMode == CVKMissingEnums.VkPresentModeKHR.VK_PRESENT_MODE_FIFO_KHR + && selectedPresentationMode != CVKMissingEnums.VkPresentModeKHR.VK_PRESENT_MODE_MAILBOX_KHR) { + selectedPresentationMode = presentationMode; + } + // Third preference is VK_PRESENT_MODE_FIFO_RELAXED_KHR + else if (presentationMode == CVKMissingEnums.VkPresentModeKHR.VK_PRESENT_MODE_FIFO_RELAXED_KHR + && selectedPresentationMode != CVKMissingEnums.VkPresentModeKHR.VK_PRESENT_MODE_MAILBOX_KHR + && selectedPresentationMode != CVKMissingEnums.VkPresentModeKHR.VK_PRESENT_MODE_FIFO_KHR) { + selectedPresentationMode = presentationMode; + } + // Last choice + else if (presentationMode == CVKMissingEnums.VkPresentModeKHR.VK_PRESENT_MODE_IMMEDIATE_KHR + && selectedPresentationMode == CVKMissingEnums.VkPresentModeKHR.VK_PRESENT_MODE_NONE) { + selectedPresentationMode = presentationMode; + } + GetLogger().info(String.format(" %s", presentationMode.name())); + } + } + + if (selectedPresentationMode == CVKMissingEnums.VkPresentModeKHR.VK_PRESENT_MODE_NONE) { + throw new RuntimeException("No presentation mode supported"); + } + } + + return ret; + } + + + + @Override + public void removeNotify() { + if (cvkRenderer != null) { + cvkRenderer.GetLogger().info("CVKCanvas removed from parent container"); + cvkRenderer.SurfaceLost(); + } + + if (surface != VK_NULL_HANDLE) { + vkDestroySurfaceKHR(CVKInstance.GetVkInstance(), surface, null); + surface = VK_NULL_HANDLE; + } + + super.removeNotify(); + } + + @Override + public void initVK() { + CVKAssertNotNull(cvkVisualProcessor); + cvkVisualProcessor.GetLogger().info("CVKCanvas initVK %d (0x%016X)", surface, surface); + + int ret = CVKDevice.GetInstance().InitialiseSurface(this); + if (VkFailed(ret)) { + throw new RuntimeException("CVKCanvas's new surface is unsupported"); + } + + ret = UpdateSurfaceCapabilities(); + if (VkFailed(ret)) { + throw new RuntimeException("CVKCanvas UpdateSurfaceCapabilities failed"); + } + + ret = SelectPresentationMode(); + if (VkFailed(ret)) { + throw new RuntimeException("CVKCanvas SelectPresentationMode failed"); + } + } + + @Override + public void paintVK() { + // This will be called by AWTVKCanvas during initialisation but before + // CVKRenderer is ready to use. + if (surface != VK_NULL_HANDLE) { + ++frameNumber; + cvkRenderer.Display(); + } + } + + @Override + public void repaint() { + if (surface != VK_NULL_HANDLE) { + if (SwingUtilities.isEventDispatchThread()) { + paintVK(); + } else { + SwingUtilities.invokeLater(() -> paintVK()); + } + } + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKDescriptorPool.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKDescriptorPool.java new file mode 100644 index 0000000000..b052bf18b4 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKDescriptorPool.java @@ -0,0 +1,178 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan; + +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKGraphLogger; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssert; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssertNotNull; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.checkVKret; +import java.nio.LongBuffer; +import org.lwjgl.system.MemoryStack; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.vulkan.VK10.VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_SUCCESS; +import static org.lwjgl.vulkan.VK10.vkCreateDescriptorPool; +import static org.lwjgl.vulkan.VK10.vkDestroyDescriptorPool; +import org.lwjgl.vulkan.VkDescriptorPoolCreateInfo; +import org.lwjgl.vulkan.VkDescriptorPoolSize; + +public class CVKDescriptorPool { + private long hDescriptorPool = VK_NULL_HANDLE; + private final CVKGraphLogger cvkGraphLogger; + + public long GetDescriptorPoolHandle() { return hDescriptorPool; } + + + /** + * Simple class for counting the number of descriptors and desciptor sets + * needed by all of the renderables. + */ + public static class CVKDescriptorPoolRequirements { + public final static int VK_DESCRIPTOR_TYPE_COUNT = 11; + public final int poolDescriptorTypeCounts[] = new int[VK_DESCRIPTOR_TYPE_COUNT]; + public int poolDesciptorSetCount = 0; + + public int GetTotalDescriptorCount() { + int count = 0; + for (int i = 0; i < VK_DESCRIPTOR_TYPE_COUNT; ++i) { + count += poolDescriptorTypeCounts[i]; + } + return count; + } + public int GetNumberOfNonEmptyTypes() { + int count = 0; + for (int i = 0; i < VK_DESCRIPTOR_TYPE_COUNT; ++i) { + if (poolDescriptorTypeCounts[i] > 0) { ++count; } + } + return count; + } + } + private final CVKDescriptorPoolRequirements cvkDescriptorPoolRequirements = new CVKDescriptorPoolRequirements(); + + + public boolean CanAccomodate(final int imageCount, CVKDescriptorPoolRequirements reqs, CVKDescriptorPoolRequirements perImageReqs) { + CVKDescriptorPoolRequirements coalesced = new CVKDescriptorPoolRequirements(); + + coalesced.poolDesciptorSetCount = reqs.poolDesciptorSetCount + + perImageReqs.poolDesciptorSetCount * imageCount; + + for (int i = 0; i < CVKDescriptorPoolRequirements.VK_DESCRIPTOR_TYPE_COUNT; ++i) { + coalesced.poolDescriptorTypeCounts[i] = reqs.poolDescriptorTypeCounts[i] + + perImageReqs.poolDescriptorTypeCounts[i] * imageCount; + } + + if (coalesced.poolDesciptorSetCount > cvkDescriptorPoolRequirements.poolDesciptorSetCount) { + return false; + } + + for (int i = 0; i < CVKDescriptorPoolRequirements.VK_DESCRIPTOR_TYPE_COUNT; ++i) { + if (coalesced.poolDescriptorTypeCounts[i] > cvkDescriptorPoolRequirements.poolDescriptorTypeCounts[i]) { + return false; + } + } + + return true; + } + + + /** + * Descriptor pools are per thread (we have one Render thread in Constellation). + * Here we create one Descriptor pool for all our Renderable objects. + * Each Renderable object is in charge of telling the Renderer how many + * Descriptors Types and Descriptor Sets it uses (IncrementDescriptorTypeRequirements) + * so here we can allocate the correct amount of memory. + * + * @param cvkDevice + * @param imageCount + * @param poolReqs + * @param perImagePoolReqs + */ + public CVKDescriptorPool(final int imageCount, + final CVKDescriptorPoolRequirements poolReqs, + final CVKDescriptorPoolRequirements perImagePoolReqs, + CVKGraphLogger logger) { + cvkGraphLogger = logger; + + CVKAssert(imageCount > 0); + CVKAssert(poolReqs != null); + CVKAssert(perImagePoolReqs != null); + + int ret = VK_SUCCESS; + + + cvkDescriptorPoolRequirements.poolDesciptorSetCount = poolReqs.poolDesciptorSetCount + + perImagePoolReqs.poolDesciptorSetCount * imageCount; + + for (int i = 0; i < CVKDescriptorPoolRequirements.VK_DESCRIPTOR_TYPE_COUNT; ++i) { + cvkDescriptorPoolRequirements.poolDescriptorTypeCounts[i] = poolReqs.poolDescriptorTypeCounts[i] + + perImagePoolReqs.poolDescriptorTypeCounts[i] * imageCount; + } + + + // Every renderable object will likely want it's own descriptor set. For some it will + // consist of a uniform buffer, a sampler and image. + + // To size the descriptor pool we need to know how many objects will have a descriptor set + // and what types are in those descriptor sets. + + // This will need to be resized periodically when new renderable objects are added to our + // scene. The descriptor pool will also need to be recreated to the appropriate size when + // the swapchain is rebuilt. + + try (MemoryStack stack = stackPush()) { + // Do we have anything to render? + int allTypesCount = cvkDescriptorPoolRequirements.GetNumberOfNonEmptyTypes(); + if (allTypesCount > 0) { + VkDescriptorPoolSize.Buffer pPoolSizes = VkDescriptorPoolSize.callocStack(allTypesCount, stack); + + int iPoolSize = 0; + for (int iType = 0; iType < 11; ++iType) { + int count = cvkDescriptorPoolRequirements.poolDescriptorTypeCounts[iType]; + if (count > 0) { + VkDescriptorPoolSize vkPoolSize = pPoolSizes.get(iPoolSize++); + vkPoolSize.type(iType); + vkPoolSize.descriptorCount(count); + cvkGraphLogger.info("Descriptor pool type %d = count %d", iType, count); + } + } + + // Create the complete Descriptor pool using the poolSizes we calculated for each + // Descriptor Type + VkDescriptorPoolCreateInfo poolInfo = VkDescriptorPoolCreateInfo.callocStack(stack); + poolInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO); + poolInfo.flags(VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT); + poolInfo.pPoolSizes(pPoolSizes); + poolInfo.maxSets(cvkDescriptorPoolRequirements.poolDesciptorSetCount); + cvkGraphLogger.info("Descriptor pool maxSets = %d", cvkDescriptorPoolRequirements.poolDesciptorSetCount); + + LongBuffer pDescriptorPool = stack.mallocLong(1); + ret = vkCreateDescriptorPool(CVKDevice.GetVkDevice(), poolInfo, null, pDescriptorPool); + checkVKret(ret); + hDescriptorPool = pDescriptorPool.get(0); + } + } finally { + CVKAssertNotNull(hDescriptorPool); + } + } + + public void Destroy() { + vkDestroyDescriptorPool(CVKDevice.GetVkDevice(), hDescriptorPool, null); + hDescriptorPool = VK_NULL_HANDLE; + cvkGraphLogger.info("Destroyed descriptor pool"); + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKDevice.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKDevice.java new file mode 100644 index 0000000000..fc986949ee --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKDevice.java @@ -0,0 +1,673 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan; + +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKGraphLogger; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKMissingEnums; +import java.nio.IntBuffer; +import java.nio.LongBuffer; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.system.MemoryUtil.NULL; + +import static org.lwjgl.vulkan.KHRSurface.vkGetPhysicalDeviceSurfaceSupportKHR; +import static org.lwjgl.vulkan.KHRSwapchain.VK_KHR_SWAPCHAIN_EXTENSION_NAME; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32B32A32_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8_SINT; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.VK_QUEUE_GRAPHICS_BIT; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_SUCCESS; +import static org.lwjgl.vulkan.VK10.VK_TRUE; +import static org.lwjgl.vulkan.VK10.vkCreateCommandPool; +import static org.lwjgl.vulkan.VK10.vkCreateDevice; +import static org.lwjgl.vulkan.VK10.vkDeviceWaitIdle; +import static org.lwjgl.vulkan.VK10.vkEnumerateDeviceExtensionProperties; +import static org.lwjgl.vulkan.VK10.vkEnumeratePhysicalDevices; +import static org.lwjgl.vulkan.VK10.vkGetDeviceQueue; +import static org.lwjgl.vulkan.VK10.vkGetPhysicalDeviceFeatures; +import static org.lwjgl.vulkan.VK10.vkGetPhysicalDeviceMemoryProperties; +import static org.lwjgl.vulkan.VK10.vkGetPhysicalDeviceProperties; +import static org.lwjgl.vulkan.VK10.vkGetPhysicalDeviceQueueFamilyProperties; +import org.lwjgl.vulkan.VkCommandPoolCreateInfo; +import org.lwjgl.vulkan.VkDevice; +import org.lwjgl.vulkan.VkDeviceCreateInfo; +import org.lwjgl.vulkan.VkDeviceQueueCreateInfo; +import org.lwjgl.vulkan.VkExtensionProperties; +import org.lwjgl.vulkan.VkExtent2D; +import org.lwjgl.vulkan.VkFormatProperties; +import org.lwjgl.vulkan.VkPhysicalDevice; +import org.lwjgl.vulkan.VkPhysicalDeviceFeatures; +import org.lwjgl.vulkan.VkPhysicalDeviceLimits; +import org.lwjgl.vulkan.VkPhysicalDeviceMemoryProperties; +import org.lwjgl.vulkan.VkPhysicalDeviceProperties; +import org.lwjgl.vulkan.VkQueue; +import org.lwjgl.vulkan.VkQueueFamilyProperties; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_DEBUGGING; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_SURFACE_UNSUPPORTED; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.GetRequiredVKLogicalDeviceExtensions; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.InitVKValidationLayers; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkFailed; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkSucceeded; +import org.lwjgl.system.MemoryUtil; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT; +import static org.lwjgl.vulkan.EXTDebugUtils.vkSetDebugUtilsObjectNameEXT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_FEATURE_BLIT_DST_BIT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_FEATURE_BLIT_SRC_BIT; +import static org.lwjgl.vulkan.VK10.VK_VERSION_MAJOR; +import static org.lwjgl.vulkan.VK10.VK_VERSION_MINOR; +import static org.lwjgl.vulkan.VK10.VK_VERSION_PATCH; +import static org.lwjgl.vulkan.VK10.vkDestroyCommandPool; +import static org.lwjgl.vulkan.VK10.vkDestroyDevice; +import static org.lwjgl.vulkan.VK10.vkGetPhysicalDeviceFormatProperties; +import org.lwjgl.vulkan.VkDebugUtilsObjectNameInfoEXT; + + +public class CVKDevice { + private static CVKDevice cvkDevice = null; + private VkPhysicalDevice vkPhysicalDevice = null; + private VkDevice vkDevice = null; + private VkQueue vkQueue = null; + private VkPhysicalDeviceProperties vkPhysicalDeviceProperties = null; + private VkPhysicalDeviceFeatures vkPhysicalDeviceFeatures = null; + private VkPhysicalDeviceMemoryProperties vkPhysicalDeviceMemoryProperties = null; + private long hCommandPool = VK_NULL_HANDLE; + private int queueFamilyIndex = -1; + private final VkExtent2D vkMaxFramebufferExtent = VkExtent2D.malloc().set(0, 0); + private int max1DImageWidth = 0; + private int max2DDimension = 0; + private int maxImageLayers = 0; + private int maxTexelBufferElements = 0; + private int minUniformBufferAlignment = 1; + private boolean vkSetDebugUtilsObjectNameEXT_available = false; + private boolean vkCmdBeginDebugUtilsLabelEXT_available = false; + private boolean vkLogicOpSupported = false; + + + // ========================> Getters <======================== \\ + + public static long GetCommandPoolHandle() { return GetInstance().hCommandPool; } + public static VkDevice GetVkDevice() { return GetInstance().vkDevice; } + public static VkPhysicalDevice GetVkPhysicalDevice() { return GetInstance().vkPhysicalDevice; } + public static VkQueue GetVkQueue() { return GetInstance().vkQueue; } + public static VkExtent2D GetMaxFrameBufferExtent() { return GetInstance().vkMaxFramebufferExtent; } + public static int GetMax1DImageWidth() { return GetInstance().max1DImageWidth; } + public static int GetMax2DDimension() { return GetInstance().max2DDimension; } + public static int GetMaxImageLayers() { return GetInstance().maxImageLayers; } + public static int GetMaxTexelBufferElements() { return GetInstance().maxTexelBufferElements; } + public static int GetMinUniformBufferAlignment() { return GetInstance().minUniformBufferAlignment; } + private static CVKGraphLogger GetLogger() { return CVKGraphLogger.GetStaticLogger(); } + public static boolean IsVkCmdBeginDebugUtilsLabelEXTAvailable() { return GetInstance().vkCmdBeginDebugUtilsLabelEXT_available; } + public static boolean AreVkLogicOpsSupported() { return GetInstance().vkLogicOpSupported; } + + + /** + * Singleton method that will create the CVKDevice. + * + * @return the one and only device + */ + public static CVKDevice GetInstance() { + if (CVKDevice.cvkDevice == null) { + cvkDevice = new CVKDevice(); + } + return cvkDevice; + } + + private CVKDevice() { + } + + + public int Initialise(long hSurface) { + int ret; + + GetLogger().StartLogSection("Initialising CVKDevice"); + try (MemoryStack stack = stackPush()) { + ret = SelectVKPhysicalDevice(stack, hSurface); + if (VkFailed(ret)) return ret; + ret = CreateVKLogicalDevice(stack); + if (VkFailed(ret)) return ret; + StoreVKQueue(stack); + ret = CreateVKCommandPool(stack); + if (VkFailed(ret)) return ret; + + if (CVK_DEBUGGING) { + // Get function pointer for the debug label annotations + if (vkDevice.getCapabilities().vkSetDebugUtilsObjectNameEXT == VK_NULL_HANDLE) { + GetLogger().warning("Failed to get pointer to vkSetDebugUtilsObjectNameEXT"); + } else { + vkSetDebugUtilsObjectNameEXT_available = true; + GetLogger().info("Found pointer to vkSetDebugUtilsObjectNameEXT"); + } + + if (vkDevice.getCapabilities().vkCmdBeginDebugUtilsLabelEXT == VK_NULL_HANDLE) { + GetLogger().warning("Failed to get pointer to vkCmdBeginDebugUtilsLabelEXT"); + } else { + vkCmdBeginDebugUtilsLabelEXT_available = true; + GetLogger().info("Found pointer to vkCmdBeginDebugUtilsLabelEXT"); + } + } + } finally { + GetLogger().EndLogSection("Initialising CVKDevice"); + } + + return ret; + } + + public int SetDebugName(long objectHandle, int objectType, String debugName) { + if (vkSetDebugUtilsObjectNameEXT_available) { + try (MemoryStack stack = stackPush()) { + VkDebugUtilsObjectNameInfoEXT objectNameInfo = VkDebugUtilsObjectNameInfoEXT.mallocStack(stack); + objectNameInfo.sType(VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT); + objectNameInfo.pNext(VK_NULL_HANDLE); + objectNameInfo.objectType(objectType); + objectNameInfo.objectHandle(objectHandle); + objectNameInfo.pObjectName(MemoryUtil.memASCII(debugName)); + + return vkSetDebugUtilsObjectNameEXT(vkDevice, objectNameInfo); + } + } + + return VK_SUCCESS; + } + + /** + * When a canvas notifies us that we have a surface available we need to + * either initialise this device if this is our first surface, or we need + * to validate the new surface. It is a Vulkan requirement that + * vkGetPhysicalDeviceSurfaceSupportKHR is called and must return VK_TRUE on + * a surface before it can be rendered to. Note in the initial case Initialise + * will call ValidateSurface during the selection of the physical device. + * + * @param canvas + * @return + */ + public int InitialiseSurface(CVKCanvas canvas) { + if (vkPhysicalDevice == null) { + return Initialise(canvas.GetSurfaceHandle()); + } else { + return ValidateSurface(vkPhysicalDevice, queueFamilyIndex, canvas.GetSurfaceHandle()); + } + } + + public void Destroy() { + try { + GetLogger().StartLogSection("Destroying CVKDevice"); + DestroyVKCommandPool(); + DestroyVKLogicalDevice(); + } finally { + GetLogger().EndLogSection("Destroying CVKDevice"); + } + } + + private int ValidateSurface(VkPhysicalDevice device, int queueFamilyIndex, long hSurface) { + try (MemoryStack stack = stackPush()) { + IntBuffer pInt = stack.mallocInt(1); + int ret = vkGetPhysicalDeviceSurfaceSupportKHR(device, queueFamilyIndex, hSurface, pInt); + if (VkFailed(ret)) return ret; + return (pInt.get(0) == VK_TRUE) ? VK_SUCCESS : CVK_SURFACE_UNSUPPORTED; + } + } + + + + /** + * Enumerates physical devices and selects the first to support swap chains + * and with a queue family that allows display (graphics operations and + * ability to present to our surface). + *

+ * Vulkan can be used for a number of roles (eg display or compute). Each + * device has a number of queue families that may be suited to a particular + * role. A queue family contains a set of queues or a single queue. These + * queues are what we submit our command buffers to. Unlike OpenGL where in + * immediate mode we submit commands one by one to the display driver, + * Vulkan batches these up in command buffers that we submit. This is more + * performant than immediate mode commands and it allows multiple threads to + * submit commands to the display driver. + *

+ * After we have a vkPhysicalDevice we then query it for a bunch of details. + *

+ *

Surface Presentation Modes

+ * There are 4 modes: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
NumNameDescription
0VK_PRESENT_MODE_IMMEDIATE_KHRI can haz screen tear
1VK_PRESENT_MODE_MAILBOX_KHRImages are queued, latest is displayed next
2VK_PRESENT_MODE_FIFO_KHRFIFO, no tearing, possible slight input lag
3VK_PRESENT_MODE_FIFO_RELAXED_KHRFIFO queue, can result in image tearing
+ * + * @param stack + */ + private int SelectVKPhysicalDevice(MemoryStack stack, long hSurface) { + int ret; + + // Get the number of physical devices + IntBuffer pInt = stack.mallocInt(1); + ret = vkEnumeratePhysicalDevices(CVKInstance.GetVkInstance(), pInt, null); + if (VkFailed(ret)) return ret; + if (pInt.get(0) == 0) { + throw new RuntimeException("Vulkan: no GPUs found"); + } + + // Get the physical devices + int numDevices = pInt.get(0); + PointerBuffer physicalDevices = stack.mallocPointer(numDevices); + ret = vkEnumeratePhysicalDevices(CVKInstance.GetVkInstance(), pInt, physicalDevices); + if (VkFailed(ret)) return ret; + + // Enumerate physical devices. Stop once requirements met and physical device set. + vkPhysicalDevice = null; + for (int iDevice = 0; (iDevice < numDevices) && vkPhysicalDevice == null; ++iDevice) { + // Get the count of extensions supported by this device + VkPhysicalDevice candidate = new VkPhysicalDevice(physicalDevices.get(iDevice), CVKInstance.GetVkInstance()); + + // Check this device supports geometry shaders + VkPhysicalDeviceFeatures candidatePhysicalDeviceFeatures = VkPhysicalDeviceFeatures.mallocStack(stack); + vkGetPhysicalDeviceFeatures(candidate, candidatePhysicalDeviceFeatures); + if (!candidatePhysicalDeviceFeatures.geometryShader()) { + GetLogger().info("Device %d discarded as it does not support geometry shaders", iDevice); + continue; + } + + // Check extensions for Swapchain support + pInt.put(0, 0); + ret = vkEnumerateDeviceExtensionProperties(candidate, (String) null, pInt, null); + if (VkFailed(ret)) return ret; + int numExtensions = pInt.get(0); + if (numExtensions > 0) { + // Get the extensions supported by this device + VkExtensionProperties.Buffer deviceExtensions = VkExtensionProperties.mallocStack(numExtensions, stack); + ret = vkEnumerateDeviceExtensionProperties(candidate, (String) null, pInt, deviceExtensions); + if (VkFailed(ret)) return ret; + + for (int iExtension = 0; iExtension < numExtensions; ++iExtension) { + String extensionName = deviceExtensions.position(iExtension).extensionNameString(); + GetLogger().info("Vulkan: device %d extension available: %s", iExtension, extensionName); + } + + // Enumerate extensions looking for swap chain support. Stop once requirements met and physical device set. + for (int iExtension = 0; (iExtension < numExtensions) && vkPhysicalDevice == null; ++iExtension) { + String extensionName = deviceExtensions.position(iExtension).extensionNameString(); + if (VK_KHR_SWAPCHAIN_EXTENSION_NAME.equals(extensionName)) { + // Spapchain tick, now check the queue families for one that can peform graphics operations + pInt.put(0, 0); + + // Calling this with no queue family properties will return the number of queue families into pInt + vkGetPhysicalDeviceQueueFamilyProperties(candidate, pInt, null); + int numQueueFamilies = pInt.get(0); + + // This populates all the properties for each queue family ie candidateQueueFamilyProperties is an array + VkQueueFamilyProperties.Buffer candidateQueueFamilyProperties = VkQueueFamilyProperties.mallocStack(numQueueFamilies, stack); + vkGetPhysicalDeviceQueueFamilyProperties(candidate, pInt, candidateQueueFamilyProperties); + + // Find a queue family that supports display. Stop once requirements met and physical device set. + for (int iQueueFamily = 0; (iQueueFamily < numQueueFamilies) && vkPhysicalDevice == null; ++iQueueFamily) { + VkQueueFamilyProperties queueFamilyProperties = candidateQueueFamilyProperties.get(iQueueFamily); + // This this queue family support graphics operations> + if ((queueFamilyProperties.queueFlags() & VK_QUEUE_GRAPHICS_BIT) != 0) { + // Can this queue family present to our surface + ret = ValidateSurface(candidate, iQueueFamily, hSurface); + if (VkSucceeded(ret)) { + queueFamilyIndex = iQueueFamily; + vkPhysicalDevice = candidate; + + if (candidatePhysicalDeviceFeatures.logicOp()) { + vkLogicOpSupported = true; + } + } + } + } //end queue family loop + } // end if extension is swapchain + } // end extension loop + } // end if numExtensions > 0 + } // end physical device loop + + if (vkPhysicalDevice != null) { + // Check we can use uniform texel buffers + VkFormatProperties vkFormatProperties = VkFormatProperties.callocStack(stack); + vkGetPhysicalDeviceFormatProperties(vkPhysicalDevice, VK_FORMAT_R32G32B32A32_SFLOAT, vkFormatProperties); + if ((vkFormatProperties.bufferFeatures() & VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT) == 0) { + throw new RuntimeException("Selected surface does not support uniform texel buffers needed for the vertex position buffer"); + } + vkGetPhysicalDeviceFormatProperties(vkPhysicalDevice, VK_FORMAT_R8_SINT, vkFormatProperties); + if ((vkFormatProperties.bufferFeatures() & VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT) == 0) { + throw new RuntimeException("Selected surface does not support uniform texel buffers needed for the vertex flags buffer"); + } + + // Happy dance, we have a suitable physical device, get its properties + vkPhysicalDeviceProperties = VkPhysicalDeviceProperties.malloc(); + vkGetPhysicalDeviceProperties(vkPhysicalDevice, vkPhysicalDeviceProperties); + + // And features + vkPhysicalDeviceFeatures = VkPhysicalDeviceFeatures.malloc(); + vkGetPhysicalDeviceFeatures(vkPhysicalDevice, vkPhysicalDeviceFeatures); + + // TODO: in order to use the smooth lines line rasterization extension we first have to + // query it is enabled. The code below didn't work and isn't a high priority atm, but + // it would be good to use some newer features on cards that support them. + +// VkPhysicalDeviceLineRasterizationFeaturesEXT physicalDeviceLineRasterFeatures = VkPhysicalDeviceLineRasterizationFeaturesEXT.malloc(); +// physicalDeviceLineRasterFeatures.sType(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT); +// physicalDeviceLineRasterFeatures.pNext(VK_NULL_HANDLE); +// +// +// VkPhysicalDeviceFeatures2 physicalDeviceFeatures2 = VkPhysicalDeviceFeatures2.malloc(); +// try { +// +// long functionHandle = vkGetInstanceProcAddr(CVKInstance.GetVkInstance(), memUTF8("vkGetPhysicalDeviceFeatures2")); +// if (functionHandle != VK_NULL_HANDLE) { +// physicalDeviceFeatures2.sType(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2); +// physicalDeviceFeatures2.pNext(physicalDeviceLineRasterFeatures.address()); +// vkGetPhysicalDeviceFeatures2(vkPhysicalDevice, physicalDeviceFeatures2); +// } +// } catch (Exception e) { +// GetLogger().LogException(e, "Oh dear"); +// } +// +// physicalDeviceLineRasterFeatures.free(); +// physicalDeviceFeatures2.free(); + + // What memory types are available + vkPhysicalDeviceMemoryProperties = VkPhysicalDeviceMemoryProperties.malloc(); + vkGetPhysicalDeviceMemoryProperties(vkPhysicalDevice, vkPhysicalDeviceMemoryProperties); + + + VkPhysicalDeviceLimits limits = vkPhysicalDeviceProperties.limits(); + max1DImageWidth = limits.maxImageDimension1D(); + max2DDimension = limits.maxImageDimension2D(); + maxImageLayers = limits.maxImageArrayLayers(); + maxTexelBufferElements = limits.maxTexelBufferElements(); + minUniformBufferAlignment = (int)limits.minUniformBufferOffsetAlignment(); + vkMaxFramebufferExtent.set(limits.maxFramebufferWidth(), limits.maxFramebufferHeight()); + + if (CVK_DEBUGGING) { + // Log device properties we're interested in + GetLogger().info("Physcial device properties:\n" + + "\tdeviceName: %s\n" + + "\tdeviceType: %s\n" + + "\tapiVersion: %d.%d.%d\n" + + "\tdriverVersion: %d\n" + + "\tvendorID: %d\n" + + "\tdeviceID: %d\n" + + "\tLimits\n" + + "\t\t maxImageDimension1D: %d\n" + + "\t\t maxImageDimension2D: %d\n" + + "\t\t maxImageDimension3D: %d\n" + + "\t\t maxImageDimensionCube: %d\n" + + "\t\t maxImageArrayLayers: %d\n" + + "\t\t maxPerStageDescriptorSamplers: %d\n" + + "\t\t maxPerStageDescriptorUniformBuffers: %d\n" + + "\t\t maxGeometryShaderInvocations: %d\n" + + "\t\t maxGeometryInputComponents: %d\n" + + "\t\t maxGeometryOutputComponents: %d\n" + + "\t\t maxGeometryOutputVertices: %d\n" + + "\t\t maxGeometryTotalOutputComponents: %d\n" + + "\t\t maxViewportDimensions: %d x %d\n" + + "\t\t viewportBoundsRange: %f x %f\n" + + "\t\t maxFramebuffer dims: %d x %d\n" + + "\t\t maxFramebufferLayers: %d\n" + + "\t\t maxColorAttachments: %d\n" + + "\t\t minMemoryMapAlignment: %d\n" + + "\t\t maxTexelBufferElements: %d\n" + + "\t\t minTexelBufferOffsetAlignment: %d\n" + + "\t\t minUniformBufferOffsetAlignment: %d\n" + + "\t\t minStorageBufferOffsetAlignment: %d\n" + + "\t\t maxPushConstantsSize: %d\n" + + "\t\t pointSizeGranularity: %f\n" + + "\t\t pointSizeRange: %f - %f\n" + + "\t\t lineWidthGranularity: %f\n" + + "\t\t lineWidthRange: %f - %f\n", + vkPhysicalDeviceProperties.deviceNameString(), + CVKMissingEnums.VkPhysicalDeviceType.GetByValue(vkPhysicalDeviceProperties.deviceType()).name(), + VK_VERSION_MAJOR(vkPhysicalDeviceProperties.apiVersion()), + VK_VERSION_MINOR(vkPhysicalDeviceProperties.apiVersion()), + VK_VERSION_PATCH(vkPhysicalDeviceProperties.apiVersion()), + vkPhysicalDeviceProperties.driverVersion(), + vkPhysicalDeviceProperties.vendorID(), + vkPhysicalDeviceProperties.deviceID(), + limits.maxImageDimension1D(), + limits.maxImageDimension2D(), + limits.maxImageDimension3D(), + limits.maxImageDimensionCube(), + limits.maxImageArrayLayers(), + limits.maxPerStageDescriptorSamplers(), + limits.maxPerStageDescriptorUniformBuffers(), + limits.maxGeometryShaderInvocations(), + limits.maxGeometryInputComponents(), + limits.maxGeometryOutputComponents(), + limits.maxGeometryOutputVertices(), + limits.maxGeometryTotalOutputComponents(), + limits.maxViewportDimensions().get(0), limits.maxViewportDimensions().get(1), + limits.viewportBoundsRange().get(0), limits.viewportBoundsRange().get(1), + limits.maxFramebufferWidth(), limits.maxFramebufferHeight(), + limits.maxFramebufferLayers(), + limits.maxColorAttachments(), + limits.minMemoryMapAlignment(), + limits.maxTexelBufferElements(), + limits.minTexelBufferOffsetAlignment(), + limits.minUniformBufferOffsetAlignment(), + limits.minStorageBufferOffsetAlignment(), + limits.maxPushConstantsSize(), + limits.pointSizeGranularity(), + limits.pointSizeRange().get(0), limits.pointSizeRange().get(1), + limits.lineWidthGranularity(), + limits.lineWidthRange().get(0), limits.lineWidthRange().get(1) + ); + } + } else { + // Sad face + GetLogger().severe("Vulkan: No suitable physical device found."); + throw new RuntimeException("Vulkan: No suitable physical device found."); + } + + return ret; + } + + public boolean CheckDeviceSupportsBlit(int sourceFormat, int destinationFormat) { + // Check and store if the device can perform blit image operations + boolean supportsBlit = true; + + try (MemoryStack stack = stackPush()) { + // Check blit support for source and destination + // TODO: do this once and store result in cvkdevice + VkFormatProperties formatProps = VkFormatProperties.callocStack(stack); + + // Check if the device supports blitting from optimal images (the swapchain images are in optimal format) + vkGetPhysicalDeviceFormatProperties(GetVkPhysicalDevice(), sourceFormat, formatProps); + + if ((formatProps.optimalTilingFeatures() & VK_FORMAT_FEATURE_BLIT_SRC_BIT) == 0) { + GetLogger().info("Device does not support blitting from optimal tiled images, using copy instead of blit!"); + supportsBlit = false; + } + + // Check if the device supports blitting to linear images + vkGetPhysicalDeviceFormatProperties(GetVkPhysicalDevice(), destinationFormat, formatProps); + if ((formatProps.linearTilingFeatures() & VK_FORMAT_FEATURE_BLIT_DST_BIT) == 0) { + GetLogger().info("Device does not support blitting to linear tiled images, using copy instead of blit!"); + supportsBlit = false; + } + } + + return supportsBlit; + } + + /** + * Create a logical device that gives us control over the physical device + * + * @param stack + * @return + */ + private int CreateVKLogicalDevice(MemoryStack stack) { + int ret = VK_SUCCESS; + + // We need the extensions and layers again. + PointerBuffer pbValidationLayers = null; + PointerBuffer pbExtensions = GetRequiredVKLogicalDeviceExtensions(stack); + if (CVK_DEBUGGING) { + pbValidationLayers = InitVKValidationLayers(); + } + + // We only need one queue + VkDeviceQueueCreateInfo.Buffer queue = VkDeviceQueueCreateInfo.mallocStack(1, stack) + .sType(VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO) + .pNext(NULL) + .flags(0) + .queueFamilyIndex(queueFamilyIndex) + .pQueuePriorities(stack.floats(1.0f)); + + //TODO: do we need this for Consty? + VkPhysicalDeviceFeatures features = VkPhysicalDeviceFeatures.callocStack(stack); + features.geometryShader(true); + if (vkPhysicalDeviceFeatures.shaderClipDistance()) { + features.shaderClipDistance(true); + } + + VkDeviceCreateInfo deviceCreateInfo = VkDeviceCreateInfo.mallocStack(stack) + .sType(VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO) + .pNext(NULL) + .flags(0) + .pQueueCreateInfos(queue) + .ppEnabledLayerNames(pbValidationLayers) + .ppEnabledExtensionNames(pbExtensions) + .pEnabledFeatures(features); + + PointerBuffer pb = stack.mallocPointer(1); + ret = vkCreateDevice(vkPhysicalDevice, deviceCreateInfo, null, pb); + if (VkSucceeded(ret)) { + vkDevice = new VkDevice(pb.get(0), vkPhysicalDevice, deviceCreateInfo); + } + return ret; + } + + private void DestroyVKLogicalDevice() { + if (vkDevice != null) { + vkDestroyDevice(vkDevice, null); + vkDevice = null; + } + } + + /** + * Initialises the queue that enables our code to communicate with the + * display device driver. We really only care about a single graphics queue. + *

+ * There are 4 queue types: + *

    + *
  1. Graphics: drawing things
  2. + *
  3. Compute: crunching equations
  4. + *
  5. Transfer: allows buffer copying (DMAing to/from video memory). Is + * implicit for the two types above.
  6. + *
  7. Sparse: supports images backed by sparse memory, think ID's + * megatexture where only part of a gigantic texture is backed by physical + * memory at any point in time.
  8. + *
+ * A queue family may contain more than one type of queue and where a type + * is present there may be one or more queues of that type. + * + * + * + * @param stack + * @see + * Queue + * reference + */ + private void StoreVKQueue(MemoryStack stack) { + PointerBuffer pb = stack.mallocPointer(1); + vkGetDeviceQueue(vkDevice, queueFamilyIndex, 0, pb); + long queueHandle = pb.get(0); + vkQueue = new VkQueue(queueHandle, vkDevice); + } + + /** + * Create the command pool from which all the command buffers are created. + * Each image in the swap chain has its own command buffer. + * + * @param stack + * @return + */ + private int CreateVKCommandPool(MemoryStack stack) { + int ret; + + VkCommandPoolCreateInfo poolInfo = VkCommandPoolCreateInfo.callocStack(stack); + poolInfo.sType(VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO); + poolInfo.queueFamilyIndex(queueFamilyIndex); + poolInfo.flags(VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT); // Hydra - added this for secondary buffers which we may not need + LongBuffer pCommandPool = stack.mallocLong(1); + ret = vkCreateCommandPool(vkDevice, poolInfo, null, pCommandPool); + if (VkSucceeded(ret)) { + hCommandPool = pCommandPool.get(0); + } + + return ret; + } + + private void DestroyVKCommandPool() { + if (hCommandPool != VK_NULL_HANDLE) { + vkDestroyCommandPool(vkDevice, hCommandPool, null); + hCommandPool = VK_NULL_HANDLE; + } + } + + public static void WaitIdle() { + assert(GetVkDevice() != null); + vkDeviceWaitIdle(GetVkDevice()); + } + + /** + * Use the mask provided by VkMemoryRequirements to return the index of the memory + * type we want that match the provided properties bit mask. + * + * The reason we need this function is that physical devices may support only some + * of the types of display memory types eg device local, host visible. In addition + * to this memory types are referred to by their type index but the same type if + * available can be at different indexes on different gpus. So this function exists + * to map the type we want to the correct index. + * + * @param typeFilter 1 << the type we want, which is the representation VkMemoryRequirements uses + * @param properties + * @return + */ + public static int GetMemoryType(int typeFilter, int properties) { + assert(cvkDevice.vkPhysicalDeviceMemoryProperties != null); + + for (int i = 0; i < cvkDevice.vkPhysicalDeviceMemoryProperties.memoryTypeCount(); ++i) { + if ((typeFilter & (1 << i)) != 0 && (cvkDevice.vkPhysicalDeviceMemoryProperties.memoryTypes(i).propertyFlags() & properties) == properties) { + return i; + } + } + + throw new RuntimeException(String.format("GetMemoryType failed to translate type %d with properties %d", typeFilter, properties)); + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKInstance.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKInstance.java new file mode 100644 index 0000000000..7ec8c01e13 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKInstance.java @@ -0,0 +1,232 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan; + +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKGraphLogger; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_DEBUGGING; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.GetRequiredVKPhysicalDeviceExtensions; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.InitVKValidationLayers; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkFailed; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkSucceeded; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.checkVKret; +import java.nio.LongBuffer; +import java.util.logging.Level; +import javax.swing.JOptionPane; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.system.MemoryUtil.NULL; +import org.lwjgl.system.NativeType; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; +import static org.lwjgl.vulkan.EXTDebugUtils.vkCreateDebugUtilsMessengerEXT; +import static org.lwjgl.vulkan.VK10.VK_API_VERSION_1_0; +import static org.lwjgl.vulkan.VK10.VK_FALSE; +import static org.lwjgl.vulkan.VK10.VK_MAKE_VERSION; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_APPLICATION_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.vkCreateInstance; +import static org.lwjgl.vulkan.VK10.vkDestroyInstance; +import static org.lwjgl.vulkan.VK10.vkGetInstanceProcAddr; +import org.lwjgl.vulkan.VkApplicationInfo; +import org.lwjgl.vulkan.VkDebugUtilsMessengerCallbackDataEXT; +import org.lwjgl.vulkan.VkDebugUtilsMessengerCreateInfoEXT; +import org.lwjgl.vulkan.VkInstance; +import org.lwjgl.vulkan.VkInstanceCreateInfo; + +public class CVKInstance { + private static CVKInstance cvkInstance = null; + private VkInstance vkInstance = null; + + private CVKInstance() {} + + /** + * Singleton method that will create the CVKInstance the first time it is + * called with validation layers if we are debugging. + * + * @return the one and only instance + */ + public static CVKInstance GetInstance() { + if (CVKInstance.cvkInstance == null) { + try (MemoryStack stack = stackPush()) { + PointerBuffer pbValidationLayers = null; + PointerBuffer pbExtensions = GetRequiredVKPhysicalDeviceExtensions(stack); + if (CVK_DEBUGGING) { + pbValidationLayers = InitVKValidationLayers(); + } + cvkInstance = new CVKInstance(); + int ret = cvkInstance.Initialise(stack, pbExtensions, pbValidationLayers, CVK_DEBUGGING); + if (ret == -7) { + //custom title, error icon + JOptionPane.showMessageDialog(null, + "Constellation requires a graphics card/driver combination that supports Vulkan. Please update your graphics card driver and try again.\n\nAlternatively: Delete the factory line at the top of the file renderer.conf to revert constellation back to openGL.", + "Vulkan Support Error", + JOptionPane.ERROR_MESSAGE); + } + if (VkFailed(ret)) { + throw new RuntimeException(String.format("CVKInstance.Initialise returned error %d", ret)); + } + return CVKInstance.cvkInstance; + } + } else { + return cvkInstance; + } + } + + + + + public static CVKInstance GetInstance(MemoryStack stack, PointerBuffer pbExtensions, PointerBuffer pbValidationLayers, boolean debugging) { + if (CVKInstance.cvkInstance == null) { + CVKInstance.cvkInstance = new CVKInstance(); + int ret = CVKInstance.cvkInstance.Initialise(stack, pbExtensions, pbValidationLayers, debugging); + if (VkFailed(ret)) { + throw new RuntimeException(String.format("CVKInstance.Initialise returned error %d", ret)); + } + } + return cvkInstance; + } + public static VkInstance GetVkInstance() { + return GetInstance().vkInstance; + } + + + public void Deinitialise() { + vkDestroyInstance(vkInstance, null); + } + + /** + * Initialises the VkInstance, the object that represents a Vulkan API + * instantiation. + *

+ * VkInstance is the LWJGL object that wraps the native cvkInstance handle. + * In OpenGL state is global, in Vulkan state is stored in the VkInstance + * object. The create info struct is used internally to create a + * VKCapabilitiesInstance. We need this because reasons. + * + * @param stack + * @param pbExtensions + * @param pbValidationLayers + * @param debugging + * @return + */ + @NativeType("VkResult") + public int Initialise(MemoryStack stack, PointerBuffer pbExtensions, PointerBuffer pbValidationLayers, boolean debugging) { + // Create the application info struct. This is a sub struct of the createinfo struct below + VkApplicationInfo appInfo = VkApplicationInfo.mallocStack(stack) + .sType(VK_STRUCTURE_TYPE_APPLICATION_INFO) + .pApplicationName(stack.UTF8("Constellation")) + .applicationVersion(VK_MAKE_VERSION(1, 0, 0)) + .pEngineName(stack.UTF8("NONE")) + .apiVersion(VK_API_VERSION_1_0); //Highest version of Vulkan supported by Constellation + + // Create the CreateInfo struct + VkInstanceCreateInfo pCreateInfo = VkInstanceCreateInfo.mallocStack() + .sType(VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO) + .pNext(0L) + .pApplicationInfo(appInfo) + .ppEnabledExtensionNames(pbExtensions) + .ppEnabledLayerNames(pbValidationLayers); + + // Debug create struct if needed + VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo = null; + if (debugging) { + debugCreateInfo = VkDebugUtilsMessengerCreateInfoEXT.callocStack(stack); + debugCreateInfo.sType(VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT); + debugCreateInfo.messageSeverity(VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT); + debugCreateInfo.messageType(VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT); + debugCreateInfo.pfnUserCallback(CVKInstance::DebugCallback); + pCreateInfo.pNext(debugCreateInfo.address()); + } + + // Create the native VkInstance and return a pointer to it's handle in pInstance + PointerBuffer pInstance = stack.pointers(1); + int ret = vkCreateInstance(pCreateInfo, null, pInstance); + if (VkSucceeded(ret)) { + long instance = pInstance.get(0); + vkInstance = new VkInstance(instance, pCreateInfo); + + // If the function exists, register a validation layer callback + if (debugging) { + assert(debugCreateInfo != null); + if (vkGetInstanceProcAddr(vkInstance, "vkCreateDebugUtilsMessengerEXT") != NULL) { + LongBuffer pDebugMessenger = stack.longs(VK_NULL_HANDLE); + checkVKret(vkCreateDebugUtilsMessengerEXT(vkInstance, debugCreateInfo, null, pDebugMessenger)); + } + } + } + + return ret; + } + + + /** + * + * API specifies this function MUST return VK_FALSE + * + * @param messageSeverity + * @param messageType + * @param pCallbackData + * @param pUserData + * @return + */ + private static int DebugCallback(int messageSeverity, int messageType, long pCallbackData, long pUserData) { + Level level; + switch (messageSeverity) { + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT: level = Level.SEVERE; break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: level = Level.WARNING; break; + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT: + case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT: + default: + level = Level.INFO; break; + } + VkDebugUtilsMessengerCallbackDataEXT callbackData = VkDebugUtilsMessengerCallbackDataEXT.create(pCallbackData); + String callbackMsg = callbackData.pMessageString(); + CVKGraphLogger.GetStaticLogger().log(level, "Validation layer: %s", callbackMsg); + + // This is an E for effort attempt to get something logged in the case + // that Constellation/JVM is crashing to desktop. + CVKGraphLogger.GetStaticLogger().Flush(); + + + // DELETE AFTER DEBUGGING +// boolean logicOp = false; +// if (callbackMsg.contains("If logic operations feature not enabled, logicOpEnable must be VK_FALSE")) { +// logicOp = true; +// } else { +// logicOp = false; +// } + +/* +ERROR +Validation layer: Validation Error: [ UNASSIGNED-CoreValidation-Shader-InterfaceTypeMismatch ] Object 0: handle = 0xe81828000000000d, type = VK_OBJECT_TYPE_SHADER_MODULE; | MessageID = 0xb6cf33fe | Type mismatch on location 0.0: 'ptr to output sint32' vs 'ptr to input arr[1] of float32' + +CAUSE +Output at location 0 of the vertex shader did not match input at location 0 of the geometry shader +*/ + + + // TODO: this has to be false because... + return VK_FALSE; + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKRenderUpdateTask.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKRenderUpdateTask.java new file mode 100644 index 0000000000..2231181d05 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKRenderUpdateTask.java @@ -0,0 +1,28 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan; + + /** + * Tasks that implement CVKRenderableUpdateTask are created in the VisualProcessor + * thread in response to user input. If those tasks have constructors that + * code will be executed in the VisualProcessor thread. Code in the run method + * is called from the render thread (AWT Event thread). + */ +@FunctionalInterface +public interface CVKRenderUpdateTask { + public void run(); +} + diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKRenderer.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKRenderer.java new file mode 100644 index 0000000000..693ffa39d1 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKRenderer.java @@ -0,0 +1,785 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan; + +import au.gov.asd.tac.constellation.utilities.color.ConstellationColor; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKMissingEnums; +import au.gov.asd.tac.constellation.utilities.graphics.Vector3f; +import au.gov.asd.tac.constellation.utilities.visual.VisualAccess; +import au.gov.asd.tac.constellation.utilities.visual.VisualChange; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool.CVKDescriptorPoolRequirements; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssert; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkFailed; +import java.awt.event.ComponentListener; +import java.nio.IntBuffer; +import org.lwjgl.system.MemoryStack; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.vulkan.VK10.*; +import org.lwjgl.vulkan.VkCommandBuffer; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.checkVKret; +import au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKGlyphTextureAtlas; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKIconTextureAtlas; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKGraphLogger; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssertNotNull; +import java.awt.event.ComponentEvent; +import static org.lwjgl.vulkan.KHRSwapchain.vkQueuePresentKHR; +import org.lwjgl.vulkan.VkClearValue; +import org.lwjgl.vulkan.VkCommandBufferBeginInfo; +import org.lwjgl.vulkan.VkCommandBufferInheritanceInfo; +import org.lwjgl.vulkan.VkOffset2D; +import org.lwjgl.vulkan.VkPresentInfoKHR; +import org.lwjgl.vulkan.VkRect2D; +import org.lwjgl.vulkan.VkRenderPassBeginInfo; +import org.lwjgl.vulkan.VkSubmitInfo; +import java.nio.LongBuffer; +import java.util.concurrent.CountDownLatch; +import static org.lwjgl.vulkan.KHRSwapchain.VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_DEBUGGING; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.io.File; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_ERROR_RENDERABLE_INITIALISATION_FAILED; + + +/* +NAMING CONVENTION FOR CLASS MEMBERS + +BLAH: static final that can be accessed from either static or instanced code +CVKBlah: a Constellation class in the au.gov.asd.tac.constellation.visual.vulkan package +cvkBlah: instance of CVKBlah class +VkBlah: Blah is LWJGL-Vulkan class, a Java object wrapping a native handle to Vulkan object +vkBlah: instance of a VkBlah class +hBlah: a handle to some object. Treat as constant after first initialised. +pBlah: this is an object that has been explicitly malloced and needs to be explicitly released (unless + a stack allocator was used to allocate it). +blah: anything else + +FUNCTION ARGUMENTS +ommit the type preface to make it clearer to differentiate the paramter from the class variable + + +CODING STANDARDS +helper classes should return error codes where possible +controller classes like CVKRenderer and aggregation classes like CVKVisualProcessor will need some other mechanism +use MemoryStack to allocate nio buffers unless an allocation needs to last longer than function scope +comment where ever code is not trivially simple +make methods static whereever possible +Favour full names over abbreviations (eg Initialise() over Init()). + +TODO: this is still not a good reason. Maybe just stylistic so resource types are differentiated from other CVK types? +Resource types should expose a Create factory method and have private constructors so that we don't have to worry +about having resource types in partially initialised states. We can also indicate failure by returning null from Create +rather than throwing exceptions from ctors. + +*/ + +public class CVKRenderer implements ComponentListener { + + // We are created and displayed by this canvas + private final CVKCanvas cvkCanvas; + private final CVKVisualProcessor cvkVisualProcessor; + private final List renderables = new ArrayList<>(); + private final List newRenderables = Collections.synchronizedList(new ArrayList<>()); + private CVKSwapChain cvkSwapChain = null; + private CVKDescriptorPool cvkDescriptorPool = null; + + private int currentFrame = 0; + private boolean swapChainNeedsRecreation = true; + private boolean descriptorPoolNeedsRecreation = true; + + // We track these as they are needed to (re)create the descriptor pool + CVKDescriptorPoolRequirements cvkDescriptorPoolRequirements = null; + CVKDescriptorPoolRequirements cvkPerImageDescriptorPoolRequirements = null; + + private boolean isExiting = false; + private final CountDownLatch exitComplete = new CountDownLatch(1); + private float clrChange = 0.01f; + private int curClrEl = 0; + private Vector3f clr = new Vector3f(0.0f, 0.0f, 0.0f); + private boolean requestScreenshot = false; + private File requestScreenshotFile = null; + + public final CVKGraphLogger GetLogger() { return cvkCanvas.GetLogger(); } + + + // ========================> Lifetime <======================== \\ + + public CVKRenderer(CVKCanvas cvkCanvas, CVKVisualProcessor cvkVisualProcessor) { + this.cvkCanvas = cvkCanvas; + this.cvkVisualProcessor = cvkVisualProcessor; + } + + private void DestroyImplementation() { + renderables.forEach(el -> { + + el.Destroy(); + }); + renderables.clear(); + + if (cvkSwapChain != null) { + cvkSwapChain.Destroy(); + cvkSwapChain = null; + } + + if (cvkDescriptorPool != null) { + cvkDescriptorPool.Destroy(); + cvkDescriptorPool = null; + } + } + + /** + * Called from CVKCanvas when it's being destroyed. + */ + public void Destroy() { + isExiting = true; + GetLogger().info("CVKRenderer.destroy() called"); + if (cvkCanvas.IsRenderThreadCurrent()) { + CVKDevice.WaitIdle(); + + DestroyImplementation(); + } else { + if (cvkCanvas.IsRenderThreadAlive()) { + try { + GetLogger().info("\tDestroy called outside of render thread. Waiting until it has finished destruction"); + exitComplete.await(); + } catch (InterruptedException e) { + GetLogger().severe(String.format("InterruptedException waiting for renderer to complete destruction %s", e)); + } + } else { + GetLogger().info("\tDestroy called outside of render thread which is no longer alive. Attempting renderer destruction."); + DestroyImplementation(); + } + } + } + + @SuppressWarnings("deprecation") + @Override + public void finalize() throws Throwable { + Destroy(); + super.finalize(); + } + + + // ========================> Renderables <======================== \\ + + public void AddRenderable(CVKRenderable renderable) { + synchronized (newRenderables) { + newRenderables.add(renderable); + } + } + + /** + * When a new renderable is added we need to: + * - call the static initialiser (in case this is the first time an instance of that + * class has been added. This can initialise shaders and descriptor layouts that + * are shared across all instances of each renderable class. + * - if the renderer has already been initialised itself it will have a CVKDevice, if + * that exists we need to initialise the new renderable instance. If it doesn't + * exist then as long as the renderable is in our renderables list it will get + * initialised when the renderer is initialised. + * - increment descriptor counts. We do this even if we don't have a swapchain so + * that when the first swapchain is created it will allocate a descriptor pool + * big enough for the renderables we currently have. + * + * @return + */ + private int ProcessNewRenderables() { + cvkCanvas.VerifyInRenderThread(); + int ret = VK_SUCCESS; + int oldRenderableCount = renderables.size(); + + synchronized (newRenderables) { + for (CVKRenderable r : newRenderables) { + try { + ret = r.Initialise(); + if (VkFailed(ret)) { return ret; } + renderables.add(r); + } catch (Exception e) { + GetLogger().LogException(e, "Exception initialising %s", r.getClass().getName()); + return CVK_ERROR_RENDERABLE_INITIALISATION_FAILED; + } + } + newRenderables.clear(); + } + + // If we added any new renderables we should recalculate the descriptor requirements + if (oldRenderableCount != renderables.size()) { + cvkDescriptorPoolRequirements = new CVKDescriptorPoolRequirements(); + cvkPerImageDescriptorPoolRequirements = new CVKDescriptorPoolRequirements(); + renderables.forEach(r -> { + r.IncrementDescriptorTypeRequirements(cvkDescriptorPoolRequirements, + cvkPerImageDescriptorPoolRequirements); + }); + } + + if (cvkDescriptorPool != null) { + descriptorPoolNeedsRecreation = !cvkDescriptorPool.CanAccomodate(cvkSwapChain.GetImageCount(), cvkDescriptorPoolRequirements, cvkPerImageDescriptorPoolRequirements); + } + + return ret; + } + + + // ========================> Surface callbacks <======================== \\ + + public void SurfaceLost() { + CVKDevice.WaitIdle(); + + for (CVKRenderable el : renderables) { + el.SetNewSwapChain(null); + el.SetNewDescriptorPool(null); + } + + if (cvkSwapChain != null) { + cvkSwapChain.Destroy(); + cvkSwapChain = null; + } + swapChainNeedsRecreation = true; + + if (cvkDescriptorPool != null) { + cvkDescriptorPool.Destroy(); + cvkDescriptorPool = null; + } + descriptorPoolNeedsRecreation = true; + } + + + // ========================> Device resources <======================== \\ + + private int RecreateSwapChain() { + CVKAssertNotNull(cvkCanvas); + CVKAssertNotNull(cvkDescriptorPoolRequirements); + CVKAssertNotNull(cvkPerImageDescriptorPoolRequirements); + cvkCanvas.VerifyInRenderThread(); + + int ret; + + // Device must be finished with all pending actions before we can + // recreate the swapchain. + CVKDevice.WaitIdle(); + + // Create a new swapchain. The number of images shouldn't change, but + // if they do each renderable will do a full destroy/create of its + // swapchain dependent resources. + CVKSwapChain newSwapChain = new CVKSwapChain(cvkCanvas); + ret = newSwapChain.Initialise(); + if (VkFailed(ret)) { return ret; } + + // Give each renderable a chance to cleanup swapchain dependent resources + for (CVKRenderable el : renderables) { + ret = el.SetNewSwapChain(newSwapChain); + if (VkFailed(ret)) { return ret; } + } + + if (cvkSwapChain != null) { + cvkSwapChain.Destroy(); + } + cvkSwapChain = newSwapChain; + + // Update the parent (CVKVisualProcessor) so it can update the shared viewport and frustum + // These will in turn be consumed by the renderables on their next ProcessRenderTasks + cvkVisualProcessor.SwapChainRecreated(cvkSwapChain); + + swapChainNeedsRecreation = false; + + return ret; + } + + private int RecreateDescriptorPool() { + cvkCanvas.VerifyInRenderThread(); + CVKAssertNotNull(cvkDescriptorPoolRequirements); + CVKAssertNotNull(cvkPerImageDescriptorPoolRequirements); + + int ret = VK_SUCCESS; + + // Device must be finished with all pending actions before we can + // recreate the swapchain or descriptor pool + CVKDevice.WaitIdle(); + + CVKDescriptorPool newDescriptorPool = new CVKDescriptorPool(cvkSwapChain.GetImageCount(), + cvkDescriptorPoolRequirements, + cvkPerImageDescriptorPoolRequirements, + GetLogger()); + CVKAssert(newDescriptorPool.GetDescriptorPoolHandle() != VK_NULL_HANDLE); + + for (CVKRenderable el : renderables) { + ret = el.SetNewDescriptorPool(newDescriptorPool); + if (VkFailed(ret)) { return ret; } + } + if (cvkDescriptorPool != null) { + cvkDescriptorPool.Destroy(); + } + cvkDescriptorPool = newDescriptorPool; + + descriptorPoolNeedsRecreation = false; + + return ret; + } + + + // ========================> Command buffers <======================== \\ + + /** + * Records/updates the Primary Command Buffer and all its Secondary Command Buffers + * + * @param stack + * @param imageIndex - index to get the current frame/image/command buffer + * @return + */ + private int RecordCommandBuffer(MemoryStack stack, int imageIndex){ + cvkCanvas.VerifyInRenderThread(); + CVKAssertNotNull(cvkSwapChain.GetFrameBufferHandle(imageIndex)); + + int ret = VK_SUCCESS; + + VkCommandBuffer primaryCommandBuffer = cvkSwapChain.GetCommandBuffer(imageIndex); + + VkCommandBufferBeginInfo beginInfo = VkCommandBufferBeginInfo.callocStack(stack); + beginInfo.sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO); + + VkClearValue.Buffer clearValues = VkClearValue.callocStack(2, stack); + clearValues.color().float32(stack.floats(clr.getR(), clr.getG(), clr.getB(), 1.0f)); + clearValues.get(1).depthStencil().set(1.0f, 0); + + VkRenderPassBeginInfo renderPassInfo = VkRenderPassBeginInfo.callocStack(stack); + renderPassInfo.sType(VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO); + renderPassInfo.renderPass(cvkSwapChain.GetRenderPassHandle()); + + VkRect2D renderArea = VkRect2D.callocStack(stack); + renderArea.offset(VkOffset2D.callocStack(stack).set(0, 0)); + renderArea.extent(cvkSwapChain.GetExtent()); + renderPassInfo.renderArea(renderArea); + renderPassInfo.pClearValues(clearValues); + renderPassInfo.framebuffer(cvkSwapChain.GetFrameBufferHandle(imageIndex)); + + // The primary command buffer does not contain any rendering commands + // These are stored (and retrieved) from the secondary command buffers + ret = vkBeginCommandBuffer(primaryCommandBuffer, beginInfo); + if (VkFailed(ret)) { return ret; } + + vkCmdBeginRenderPass(primaryCommandBuffer, renderPassInfo, VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS); + + // Inheritance info for the secondary command buffers (same for all!) + VkCommandBufferInheritanceInfo inheritanceInfo = VkCommandBufferInheritanceInfo.callocStack(stack); + inheritanceInfo.sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO); + inheritanceInfo.pNext(0); + inheritanceInfo.framebuffer(cvkSwapChain.GetFrameBufferHandle(imageIndex)); + inheritanceInfo.renderPass(cvkSwapChain.GetRenderPassHandle()); + inheritanceInfo.subpass(0); // Get the subpass of make it here? + inheritanceInfo.occlusionQueryEnable(false); + inheritanceInfo.queryFlags(0); + inheritanceInfo.pipelineStatistics(0); + + // Loop through renderables and record their buffers + for (CVKRenderable el : renderables) { + if (CVK_DEBUGGING && el.DebugSkipRender()) { + continue; + } + if (el.GetVertexCount() > 0) { + ret = el.RecordDisplayCommandBuffer(inheritanceInfo, imageIndex); + if (VkFailed(ret)) { return ret; } + + // TODO: may be more efficient to add all the visible command buffers to a master list then + // call the following line once with the whole list + VkCommandBuffer commandBuffer = el.GetDisplayCommandBuffer(imageIndex); + if (commandBuffer != null) { + vkCmdExecuteCommands(primaryCommandBuffer, commandBuffer); + } + } + } + + vkCmdEndRenderPass(primaryCommandBuffer); + checkVKret(vkEndCommandBuffer(primaryCommandBuffer)); + + if (CVK_DEBUGGING) { + Debug_UpdateRGB(); + } + + return ret; + } + + /** + * + * API calls in this method are asynchronous and need to be synchronised. + * + * @param stack + * @param pImageAcquiredSemaphore + * @param pCommandBufferExecutedSemaphore + * @param commandBuffer + * @param hRenderFence + * @return + */ + public int ExecuteCommandBuffer(MemoryStack stack, + LongBuffer pImageAcquiredSemaphore, + LongBuffer pCommandBufferExecutedSemaphore, + VkCommandBuffer commandBuffer, + long hRenderFence) { + CVKAssert(pImageAcquiredSemaphore != null && pImageAcquiredSemaphore.get(0) != VK_NULL_HANDLE); + CVKAssert(pCommandBufferExecutedSemaphore != null && pCommandBufferExecutedSemaphore.get(0) != VK_NULL_HANDLE); + CVKAssert(commandBuffer != null); + + VkSubmitInfo submitInfo = VkSubmitInfo.callocStack(stack); + submitInfo.sType(VK_STRUCTURE_TYPE_SUBMIT_INFO); + submitInfo.pCommandBuffers(stack.pointers(commandBuffer)); + submitInfo.pWaitSemaphores(pImageAcquiredSemaphore); + submitInfo.waitSemaphoreCount(1); + submitInfo.pWaitDstStageMask(stack.ints(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT)); + submitInfo.pSignalSemaphores(pCommandBufferExecutedSemaphore); + + return vkQueueSubmit(CVKDevice.GetVkQueue(), + submitInfo, + hRenderFence); + } + + + // ========================> Display <======================== \\ + + /** + * + * API calls in this method are asynchronous and need to be synchronised. + * + * @param stack + * @param pCommandExecutionSemaphore + * @param imageIndex + * @return + */ + private int ReturnImageToSwapchainAndPresent(MemoryStack stack, LongBuffer pCommandExecutionSemaphore, int imageIndex) { + CVKAssertNotNull(CVKDevice.GetVkQueue()); + + VkPresentInfoKHR presentInfo = VkPresentInfoKHR.callocStack(stack); + presentInfo.sType(VK_STRUCTURE_TYPE_PRESENT_INFO_KHR); + presentInfo.pWaitSemaphores(pCommandExecutionSemaphore); + presentInfo.swapchainCount(1); + presentInfo.pSwapchains(stack.longs(cvkSwapChain.GetSwapChainHandle())); + presentInfo.pImageIndices(stack.ints(imageIndex)); + return vkQueuePresentKHR(CVKDevice.GetVkQueue(), presentInfo); + } + + /** + * No parrellisation of CPU/GPU, eg only one swap chain image and set of command buffers + * + * + * + *
CPUPrepare image 0Prepare image 0
GPUPresent image 0Present image 0
+ *

+ * 2 swapchain images with synchronisation of CPU preparation and GPU execution/presentation + * + * + * + *
CPUPrepare image 0Prepare image 1Prepare image 0Prepare image 1
GPUPresent image 1Present image 0Present image 1Present image 0
+ *

+ * + *

SYNCHRONISATION

+ * There are several points that need to be synchronised: + *

+ * 1. Acquiring the next available image from the swap chain. The image the swap chain + * returns to us depends on the state of the images in flight and the policy used to + * create the swap chain. See the initialisation of CVKDevice.selectedPresentationMode + * for more details. + * vkAcquireNextImageKHR will return immediately with the index of the image that will be + * acquired for rendering and presenting, but the image itself may not be ready yet to be + * rendered to or presented. For that we need to provide either a fence or semaphore to + * vkAcquireNextImageKHR that will be signaled when the image is ready. We can pass that + * semaphore into vkQueueSubmit when we submit our command buffers so the execution of + * commands waits until the image is ready. + *

+ * 2. Presenting an image. vkQueuePresentKHR adds an acquired image to the presentation queue + * which once drained unacquires the image and returns it to the swapchain. Before we + * enqueue an image into the presentation queue we need to ensure our rendering (execution + * of command buffers) has completed. + * + * Frame 0: + * 1. acquire image index 0 (get imageAcquisitionSemaphore 0) + * 2. wait on that image 0 being ready + * 3. execute command buffer 0 (return imageAcquisitionSemaphore 0 and get commandBufferExecutedSemaphore 0) + * 4. wait for command buffer 0 to execute + * 5. present image 0 + * + * !Problem: commandBufferExecutedSemaphore is never returned. Maybe it never needs to be returned as we + * have to wait for imageAcquisitionSemaphore 0 which implies commandBufferExecutedSemaphore 0 will have + * already been signaled. If that is the case then we only need logic for the image acquisition semaphore. + * We could bind them together, like the CVKFrame concept. + * + * Frame 1: + * 1. acquire image index 1 + * 2. wait on that image 1 being ready + * 3. execute command buffer 1 + * 4. wait for command buffer 1 to execute + * 5. present image 1 + * + * Frame 2 + * + * 0. wait on imageAcquisitionSemaphore 0, is this necessary? + * 1. acquire image index 0 + * 2. wait on that image 0 being ready + * 3. execute command buffer 0 + * 4. wait for command buffer 0 to execute + * 5. present image 0 + * + * Commands may be enqueued in order but without synchronisation they can be processed and + * complete in any order. We often require a particular state before an operation occurs, + * for example we require an image is transitioned to the destination transfer optimal + * state before we copy data into it. In order to enforce required states we use pipeline + * barriers. VkImageMemoryBarrier is the type of barrier we use in the previous example, + * not only does this barrier block latter commands until our destination stage is reached, + * it is also responsible for the transition from source to destination state. + * + * Pipeline barriers (image transitions). + * + * + */ + public void Display() { + int ret; + + if (isExiting) { + if (CVKDevice.GetVkDevice() != null) { + CVKDevice.WaitIdle(); + DestroyImplementation(); + exitComplete.countDown(); + } + return; + } + + // Our render thread should be the AWT thread that owns the canvas, whose + // surface is our render target. Being called by any other thread will + // lead to resource contention and deadlock (seen during development when + // user events were handled immediately rather than enqueueing them for + // the render thread to handle). + cvkCanvas.VerifyInRenderThread(); + + // Adding renderables will change descriptor requirements, we need to update + // these before the swapchain is initialised for the first time. + ret = ProcessNewRenderables(); + checkVKret(ret); + + if (cvkSwapChain == null || swapChainNeedsRecreation) { + ret = RecreateSwapChain(); + if (VkFailed(ret)) { + GetLogger().warning("recreate swapchain failed, skipping this display"); + return; + } + } + if (cvkDescriptorPool == null || descriptorPoolNeedsRecreation) { + ret = RecreateDescriptorPool(); + checkVKret(ret); + } + + if (cvkSwapChain != null) { + // Process updates enqueued by other threads. If any display resources need to be + // (re)created then the renderable involved needs to set a flag so the next time + // NeedsDisplayUpdate is called it returns true and is given a chance to update + // while no GPU actions are pending. + ret = cvkVisualProcessor.ProcessRenderTasks(cvkSwapChain); + checkVKret(ret); + + // If any renderable holds a resource shared across frames then to + // recreate it we need to a complete halt, that is we need all in + // flight images to have been presented and all fences available. + // Note the one liner was created by Netbeans, I am not convinced it's + // very clear. + boolean updateDisplays = false; + for (CVKRenderable el : renderables) { + updateDisplays = el.NeedsDisplayUpdate(); + if (updateDisplays) break; + } + + // Blocking updates + if (updateDisplays) { + CVKDevice.WaitIdle(); + ret = CVKIconTextureAtlas.GetInstance().DisplayUpdate(); + checkVKret(ret); + ret = CVKGlyphTextureAtlas.GetInstance().DisplayUpdate(); + checkVKret(ret); + for (CVKRenderable el : renderables) { + if (el.NeedsDisplayUpdate()) { + ret = el.DisplayUpdate(); + checkVKret(ret); + } + } + } else { + // TODO: remove once the shouldRender code is figured out. + return; + } + } + + // If the surface is not ready RecreateSwapChain won't have reset this flag + if (cvkSwapChain != null && !swapChainNeedsRecreation && cvkDescriptorPool != null && !descriptorPoolNeedsRecreation) { + try (MemoryStack stack = stackPush()) { + // The swapchain decides which image we should render to based on the + // mode we created with. + IntBuffer pImageIndex = stack.mallocInt(1); + LongBuffer pImageAcquisitionSemaphore = stack.mallocLong(1); + ret = cvkSwapChain.AcquireNextImage(stack, pImageIndex, pImageAcquisitionSemaphore); + if (ret == CVKMissingEnums.VkResult.VK_SUBOPTIMAL_KHR.Value() + || ret == CVKMissingEnums.VkResult.VK_ERROR_OUT_OF_DATE_KHR.Value()) { + swapChainNeedsRecreation = true; + } else { + checkVKret(ret); + int imageIndex = pImageIndex.get(0); + + // The two semaphores tell us when an image is ready and when we've finished writing + // to our command buffers, they don't tell us when the GPU is finished with the command + // buffers. For that we need a fence, this is a synchronisation structure that is + // device writable and host readable. + ret = cvkSwapChain.WaitOnFence(imageIndex); + checkVKret(ret); + + // Record each renderables commands into secondary buffers and add them to the + // primary command buffer. + ret = RecordCommandBuffer(stack, imageIndex); + checkVKret(ret); + + // This will wait for the image at imageIndex to be acquired then submit + // our primary command buffer to the execution queue. Once executed + // hCommandBufferExecutedSemaphore will be signaled and we can present. + LongBuffer pCommandExecutionSemaphore = stack.mallocLong(1); + ret = cvkSwapChain.GetCommandBufferExecutedSemaphore(imageIndex, pCommandExecutionSemaphore); + checkVKret(ret); + ret = ExecuteCommandBuffer(stack, + pImageAcquisitionSemaphore, + pCommandExecutionSemaphore, + cvkSwapChain.GetCommandBuffer(imageIndex), + cvkSwapChain.GetFence(imageIndex)); + checkVKret(ret); + + // Render fences synchronise CPU-GPU access to shared memory. The + // fence we want to use will be in the signalled state as either it + // was just created (we create them in the signalled state) or the + // GPU will have signalled it's finished with. We now reset it to + // the unsignalled state prior to ReturnImageToSwapchainAndPresent + // so that we can wait on the GPU to signal it again. + //frame.ResetRenderFence(); + ret = ReturnImageToSwapchainAndPresent(stack, + pCommandExecutionSemaphore, + imageIndex); + if (ret == CVKMissingEnums.VkResult.VK_SUBOPTIMAL_KHR.Value() + || ret == CVKMissingEnums.VkResult.VK_ERROR_OUT_OF_DATE_KHR.Value()) { + swapChainNeedsRecreation = true; + } else { + checkVKret(ret); + } + + // Offscreen Render Pass + List hitTestRenderables = cvkVisualProcessor.GetHitTesterList(); + ret = cvkVisualProcessor.GetHitTester().OffscreenRender(hitTestRenderables); + checkVKret(ret); + + if (requestScreenshot) { + CVKDevice.WaitIdle(); + cvkSwapChain.GetImage(imageIndex).SaveToFile(requestScreenshotFile); + requestScreenshot = false; + requestScreenshotFile = null; + } + + // Move the frame index to the next cab off the rank + currentFrame = (++currentFrame) % cvkSwapChain.GetImageCount(); + } + } + } + + // We'll need another frame to recreate the swapchain. swapChainNeedsRecreation + // can be set if submitting an image to the render queue returned suboptimal or out of date. + if (swapChainNeedsRecreation) { + cvkVisualProcessor.requestRedraw(); + } + + // The visual processor will not trigger any more updates until this is signalled + cvkVisualProcessor.signalProcessorIdle(); + } + + /** + * @param file to save the screenshot to + * @return + */ + public CVKRenderUpdateTask TaskRequestScreenshot(File file) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + requestScreenshot = true; + requestScreenshotFile = file; + }; + } + + public CVKRenderUpdateTask BackgroundColourChanged(final VisualChange change, final VisualAccess access) { + final ConstellationColor backgroundColour = access.getBackgroundColor(); + + return () -> { + clr.setR(backgroundColour.getRed()); + clr.setG(backgroundColour.getGreen()); + clr.setB(backgroundColour.getBlue()); + }; + } + + int[] getViewport() { + final int[] viewport = new int[4]; + viewport[0] = 0; + viewport[1] = 0; + viewport[2] = cvkSwapChain.GetWidth(); + viewport[3] = cvkSwapChain.GetHeight(); + return viewport; + } + + private void Debug_UpdateRGB(){ + final float MAX = 0.7f; // don't go to white, it's hard to see points + // When the current component reaches it's limit do something + if (clr.a[curClrEl] >= MAX || clr.a[curClrEl] < 0.0f) { + // Clamp it to 0-1 + clr.a[curClrEl] = Math.max(0.0f, Math.min(MAX, clr.a[curClrEl])); + // If we have hit the ceiling or floor on all components, change direction + if (curClrEl == 2) { + clrChange = -clrChange; + } + // Start changing the next component + curClrEl = (curClrEl + 1) % 3; + } + // Walk the current component a little + clr.a[curClrEl] += clrChange; + } + + + // ========================> Component listener <======================== \\ + + @Override + public void componentResized(ComponentEvent e) { + // Hydra - This callback is quite spammy, resulting in the swapchain being + // recreated many times in a row. However, there is a bug in some of + // the Vulkan drivers where the VK_SUBOPTIMAL_KHR and + // VK_ERROR_OUT_OF_DATE_KHR messages are not being generated from + // vkAcquireNextImageKHR() or vkQueuePresentKHR. Updating the GFX drivers + // fixed the issue on my system. Unfortunately we can't expect users + // to be running with the correct drivers so instead we have to do it + // this way to flag a recreation. + // TODO: Test how this performs when we have a big graph + + // Reference: https://bugs.freedesktop.org/show_bug.cgi?id=111097 + swapChainNeedsRecreation = true; + } + @Override + public void componentHidden(ComponentEvent e) { + GetLogger().info("Canvas sent "); + } + @Override + public void componentMoved(ComponentEvent e) { + GetLogger().info("Canvas moved"); + } + @Override + public void componentShown(ComponentEvent e) { + GetLogger().info("Canvas shown"); + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKSwapChain.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKSwapChain.java new file mode 100644 index 0000000000..02109ae409 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKSwapChain.java @@ -0,0 +1,766 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan; + +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKMissingEnums.VkFormat.VK_FORMAT_NONE; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssert; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkFailed; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkSucceeded; +import java.nio.IntBuffer; +import java.nio.LongBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.vulkan.KHRSurface.VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; +import static org.lwjgl.vulkan.KHRSwapchain.VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; +import static org.lwjgl.vulkan.KHRSwapchain.VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; +import static org.lwjgl.vulkan.KHRSwapchain.vkCreateSwapchainKHR; +import static org.lwjgl.vulkan.KHRSwapchain.vkGetSwapchainImagesKHR; +import static org.lwjgl.vulkan.VK10.VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; +import static org.lwjgl.vulkan.VK10.VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; +import static org.lwjgl.vulkan.VK10.VK_ATTACHMENT_LOAD_OP_CLEAR; +import static org.lwjgl.vulkan.VK10.VK_ATTACHMENT_LOAD_OP_DONT_CARE; +import static org.lwjgl.vulkan.VK10.VK_ATTACHMENT_STORE_OP_DONT_CARE; +import static org.lwjgl.vulkan.VK10.VK_ATTACHMENT_STORE_OP_STORE; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_UNDEFINED; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_VIEW_TYPE_2D; +import static org.lwjgl.vulkan.VK10.VK_PIPELINE_BIND_POINT_GRAPHICS; +import static org.lwjgl.vulkan.VK10.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; +import static org.lwjgl.vulkan.VK10.VK_SAMPLE_COUNT_1_BIT; +import static org.lwjgl.vulkan.VK10.VK_SHARING_MODE_EXCLUSIVE; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_SUBPASS_EXTERNAL; +import static org.lwjgl.vulkan.VK10.VK_SUCCESS; +import static org.lwjgl.vulkan.VK10.vkAllocateCommandBuffers; +import static org.lwjgl.vulkan.VK10.vkCreateFramebuffer; +import static org.lwjgl.vulkan.VK10.vkCreateRenderPass; +import org.lwjgl.vulkan.VkAttachmentDescription; +import org.lwjgl.vulkan.VkAttachmentReference; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkCommandBufferAllocateInfo; +import org.lwjgl.vulkan.VkFramebufferCreateInfo; +import org.lwjgl.vulkan.VkRenderPassCreateInfo; +import org.lwjgl.vulkan.VkSubpassDependency; +import org.lwjgl.vulkan.VkSubpassDescription; +import org.lwjgl.vulkan.VkSurfaceCapabilitiesKHR; +import org.lwjgl.vulkan.VkSwapchainCreateInfoKHR; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.UINT64_MAX; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.checkVKret; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKImage; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKSwapChainImage; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKGraphLogger; +import static org.lwjgl.vulkan.KHRSwapchain.vkAcquireNextImageKHR; +import static org.lwjgl.vulkan.KHRSwapchain.vkDestroySwapchainKHR; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_LEVEL_PRIMARY; +import static org.lwjgl.vulkan.VK10.VK_FENCE_CREATE_SIGNALED_BIT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_D24_UNORM_S8_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_D32_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_D32_SFLOAT_S8_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_UNDEFINED; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_ASPECT_DEPTH_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_TILING_OPTIMAL; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_USAGE_TRANSFER_SRC_BIT; +import static org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.vkCreateFence; +import static org.lwjgl.vulkan.VK10.vkCreateSemaphore; +import static org.lwjgl.vulkan.VK10.vkDestroyFence; +import static org.lwjgl.vulkan.VK10.vkDestroyFramebuffer; +import static org.lwjgl.vulkan.VK10.vkDestroyRenderPass; +import static org.lwjgl.vulkan.VK10.vkDestroySemaphore; +import static org.lwjgl.vulkan.VK10.vkFreeCommandBuffers; +import static org.lwjgl.vulkan.VK10.vkGetPhysicalDeviceFormatProperties; +import static org.lwjgl.vulkan.VK10.vkResetFences; +import static org.lwjgl.vulkan.VK10.vkWaitForFences; +import org.lwjgl.vulkan.VkExtent2D; +import org.lwjgl.vulkan.VkFenceCreateInfo; +import org.lwjgl.vulkan.VkFormatProperties; +import org.lwjgl.vulkan.VkSemaphoreCreateInfo; + +/** + * This class owns the presentable Vulkan swapchain. It is a collection of presentable + * images as well as the procedure used for presentation (eg single, double or triple + * buffered, singular or stereoscopic). + * + * Swapchains are regularly destroyed when our rendering surface is resized or + * recreated (which could happen in response to users changing system display + * properties). + * + * For most resources controlled by the swapchain there will be an instance held + * for each image in the swapchain. This is so we can utilise Vulkan's buffered + * approach to displaying, that is submitting a completed list of commands and + * resources and updating the next set before the first has been presented. + */ +public class CVKSwapChain { + private final CVKCanvas cvkCanvas; + private CVKImage cvkDepthImage = null; + private long hSwapChainHandle = VK_NULL_HANDLE; + private long hRenderPassHandle = VK_NULL_HANDLE; + private long hOffscreenRenderPassHandle = VK_NULL_HANDLE; + private int imageCount = 0; + private int depthFormat = VK_FORMAT_UNDEFINED; + private List swapChainImages = null; + private List framebufferHandles = null; + private List imageAcquisitionHandles = null; + private List commandExecutionHandles = null; + private List renderFenceHandles = null; + private int nextImageAcquisitionIndex = 0; + private List commandBuffers = null; + private final VkExtent2D vkCurrentImageExtent = VkExtent2D.malloc().set(0,0); + + + public int GetImageCount() { return imageCount; } + public CVKImage GetImage(int imageIndex) { + return swapChainImages.get(imageIndex); + } + public long GetSwapChainHandle() { return hSwapChainHandle; } + public long GetRenderPassHandle() { return hRenderPassHandle; } + public long GetOffscreenRenderPassHandle() { return hOffscreenRenderPassHandle; } + public long GetFrameBufferHandle(int imageIndex) { return framebufferHandles.get(imageIndex); } + public VkCommandBuffer GetCommandBuffer(int imageIndex) { return commandBuffers.get(imageIndex); } + public long GetFence(int imageIndex) { return renderFenceHandles.get(imageIndex); } + public int GetDepthFormat() { return depthFormat; } + public int GetColorFormat() { return cvkCanvas.GetSurfaceFormat().Value(); } + private CVKGraphLogger GetLogger() { return cvkCanvas.GetLogger(); } + + public CVKSwapChain(CVKCanvas canvas) { + cvkCanvas = canvas; + } + + + public int Initialise() { + int ret; + + try (MemoryStack stack = stackPush()) { + GetLogger().StartLogSection("Init SwapChain"); + ret = InitVKSwapChain(stack); + if (VkFailed(ret)) return ret; + ret = InitVKRenderPass(stack); + if (VkFailed(ret)) return ret; + ret = CreateOffscreenRenderPass(stack); + if (VkFailed(ret)) return ret; + ret = InitVKFrameBuffer(stack); + if (VkFailed(ret)) return ret; + ret = InitVKCommandBuffers(stack); + if (VkFailed(ret)) return ret; + } finally { + GetLogger().EndLogSection("Init SwapChain"); + } + return ret; + } + + + public void Destroy() { + GetLogger().StartLogSection("Destroy SwapChain"); + + DestroyVKCommandBuffers(); + DestroyVKFrameBuffer(); + DestroyVKRenderPass(); + DestroyVKOffscreenRenderPass(); + DestroyVKSwapChain(); + + CVKAssert(cvkDepthImage == null); + CVKAssert(hSwapChainHandle == VK_NULL_HANDLE); + CVKAssert(hRenderPassHandle == VK_NULL_HANDLE); + CVKAssert(hOffscreenRenderPassHandle == VK_NULL_HANDLE); + CVKAssert(framebufferHandles.isEmpty()); + CVKAssert(imageAcquisitionHandles.isEmpty()); + CVKAssert(commandExecutionHandles.isEmpty()); + CVKAssert(renderFenceHandles.isEmpty()); + CVKAssert(commandBuffers.isEmpty()); + + GetLogger().EndLogSection("Destroy SwapChain"); + } + + /** + * Initialises a swap chain which is the mechanism used to present images. + *

+ * A swap chain is essentially an array of displayable buffers (vkImages). + * They can be configured for direct rendering, double or triple buffered + * rendering or even stereoscopic rendering. + *

+ * This method also selects the number of images in the swapchain. Currently + * there is a copy of many rendering resources (eg vertex buffers, uniform + * buffers) that can probably be removed as Constellation isn’t continuously + * rendering. + *

+ * When created vkImages contain a logical allocation but this has not been + * backed by physical memory yet. The 3 steps to actually getting memory + * are: + *

    + *
  1. get allocation requirements, affected by mips, layers etc
  2. + *
  3. allocate a chunk of suitable memory on the device
  4. + *
  5. bind that allocation to the vkImage
  6. + *
+ * vkCreateImageView takes care of all 3 steps through the VkImageViewCreateInfo + * structure. + * + * @param stack + * @return + * @see + * Swapchain + * reference + */ + private int InitVKSwapChain(MemoryStack stack) { + int ret; + + // Update the ideal extent for our backbuffer as it may have changed + ret = cvkCanvas.UpdateSurfaceCapabilities(); + if (VkFailed(ret)) return ret; + + // Double buffering is preferred + VkSurfaceCapabilitiesKHR vkSurfaceCapablities = cvkCanvas.GetSurfaceCapabilities(); + IntBuffer pImageCount = stack.ints(vkSurfaceCapablities.minImageCount() + 1); + if (vkSurfaceCapablities.maxImageCount() > 0 && pImageCount.get(0) > vkSurfaceCapablities.maxImageCount()) { + pImageCount.put(0, vkSurfaceCapablities.maxImageCount()); + } + imageCount = pImageCount.get(0); + GetLogger().log(Level.INFO, "Swapchain will have %d images", imageCount); + if (imageCount == 0) { + throw new RuntimeException("Swapchain cannot have 0 images"); + } + + + // TODO: this needs a lot of commenting + vkCurrentImageExtent.set(cvkCanvas.GetCurrentSurfaceExtent()); + VkSwapchainCreateInfoKHR createInfo = VkSwapchainCreateInfoKHR.callocStack(stack); + createInfo.sType(VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR); + createInfo.surface(cvkCanvas.GetSurfaceHandle()); + createInfo.minImageCount(imageCount); + createInfo.imageFormat(cvkCanvas.GetSurfaceFormat().Value()); + createInfo.imageColorSpace(cvkCanvas.GetSurfaceColourSpace().Value()); + createInfo.imageExtent(vkCurrentImageExtent); + createInfo.imageArrayLayers(1); + // VK_IMAGE_USAGE_TRANSFER_SRC_BIT flag is needed as we support screenshots of the swapchain image + createInfo.imageUsage(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT ); + createInfo.imageSharingMode(VK_SHARING_MODE_EXCLUSIVE); + createInfo.preTransform(vkSurfaceCapablities.currentTransform()); + createInfo.compositeAlpha(VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR); + createInfo.presentMode(cvkCanvas.GetPresentationMode().Value()); + createInfo.clipped(true); + createInfo.oldSwapchain(VK_NULL_HANDLE); + + // Create the swapchain + LongBuffer pSwapChainHandles = stack.longs(VK_NULL_HANDLE); + ret = vkCreateSwapchainKHR(CVKDevice.GetVkDevice(), createInfo, null, pSwapChainHandles); + if (VkFailed(ret)) return ret; + hSwapChainHandle = pSwapChainHandles.get(0); + + // Check this swapchain supports the number of images we requested + ret = vkGetSwapchainImagesKHR(CVKDevice.GetVkDevice(), hSwapChainHandle, pImageCount, null); + if (VkFailed(ret)) return ret; + // TODO: exception? + CVKAssert(imageCount == pImageCount.get(0)); + + // Get the handles for each image + LongBuffer pSwapchainImageHandles = stack.mallocLong(imageCount); + ret = vkGetSwapchainImagesKHR(CVKDevice.GetVkDevice(), hSwapChainHandle, pImageCount, pSwapchainImageHandles); + if (VkFailed(ret)) return ret; + + // Store the native handle for each image and create a view for it + swapChainImages = new ArrayList<>(imageCount); + imageAcquisitionHandles = new ArrayList<>(); + commandExecutionHandles = new ArrayList<>(); + renderFenceHandles = new ArrayList<>(); + for(int i = 0; i < imageCount; ++i) { + long swapChainImageHandle = pSwapchainImageHandles.get(i); + + // Factory that returns a SwapChainImage instance initialised with device, image handle, + // sets default parameters and creates the Image View + swapChainImages.add(CVKSwapChainImage.Create(swapChainImageHandle, cvkCanvas.GetSurfaceFormat().Value(), String.format("CVKSwapChainIamge %d", i))); + swapChainImages.get(i).SetExtent(cvkCanvas.GetCurrentSurfaceExtent().width(),cvkCanvas.GetCurrentSurfaceExtent().height()); + + // Create synchronisation objects, we recreate these each time the swapchain is recreated + // which is probably excessive, though it's theoretically possible the recreated swapchain + // could have a different number of images to its precursor (though probably impossible in + // reality). + LongBuffer pSemaphore = stack.mallocLong(1); + VkSemaphoreCreateInfo semaphoreInfo = VkSemaphoreCreateInfo.callocStack(stack); + semaphoreInfo.sType(VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO); + ret = vkCreateSemaphore(CVKDevice.GetVkDevice(), semaphoreInfo, null, pSemaphore); + if (VkFailed(ret)) { return ret; } + imageAcquisitionHandles.add(pSemaphore.get(0)); + pSemaphore.put(0, 0); + ret = vkCreateSemaphore(CVKDevice.GetVkDevice(), semaphoreInfo, null, pSemaphore); + if (VkFailed(ret)) { return ret; } + commandExecutionHandles.add(pSemaphore.get(0)); + + // Render fence, CPU-GPU synch. Created signalled so we can wait on it during the very + // first frame before anything has been sent to the GPU and have it just return immediately. + VkFenceCreateInfo fenceInfo = VkFenceCreateInfo.callocStack(stack); + fenceInfo.sType(VK_STRUCTURE_TYPE_FENCE_CREATE_INFO); + fenceInfo.flags(VK_FENCE_CREATE_SIGNALED_BIT); + LongBuffer pFence = stack.mallocLong(1); + ret = vkCreateFence(CVKDevice.GetVkDevice(), fenceInfo, null, pFence); + if (VkFailed(ret)) { return ret; } + renderFenceHandles.add(pFence.get(0)); + } + + // Figure out the best depth format our device supports + VkFormatProperties vkFormatProperties = VkFormatProperties.callocStack(stack); + IntBuffer possibleDepthFormats = stack.ints(VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT); + for (int i = 0; i < possibleDepthFormats.capacity(); ++i) { + vkGetPhysicalDeviceFormatProperties(CVKDevice.GetVkPhysicalDevice(), + possibleDepthFormats.get(i), + vkFormatProperties); + if ((vkFormatProperties.optimalTilingFeatures() & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0) { + depthFormat = possibleDepthFormats.get(i); + break; + } + } + if (depthFormat == VK_FORMAT_UNDEFINED) { + GetLogger().severe("Failed to find supported detpth format"); + throw new RuntimeException("Failed to find supported detpth format"); + } + + // Depth attachment. It has: + // * the same extents as the colour images + // * a single layer + // * best format determined in the block above + // * optimal tiling (square kernel vs linear row) for performance + // * depth/stencil usage + // * device local, the fastest memory type, not host readable/writable + // but the gpu is the only thing that writes and then reads depth. + cvkDepthImage = CVKImage.Create(vkCurrentImageExtent.width(), + vkCurrentImageExtent.height(), + 1, + depthFormat, + VK_IMAGE_VIEW_TYPE_2D, + VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + VK_IMAGE_ASPECT_DEPTH_BIT, + null, + GetLogger(), + "CVKSwapChain cvkDepthImage"); + cvkDepthImage.Transition(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + + return ret; + } + + /** + * In Vulkan the swap chain owns the presentable images. A frame buffer is + * a collection of attachments used by a render pass. These attachments correspond + * to the images owned by the swapchain. There will be one frame buffer per + * image in the swap chain. The depth image is used by all frame buffers as + * we never present it so once we get to the last fragment in the last fragment + * shader it is free to be cleared and used by the next frame. + * + * @param stack + * @return + * @see LunarG Framebuffers + */ + private int InitVKFrameBuffer(MemoryStack stack) { + CVKAssert(vkCurrentImageExtent.width() > 0); + CVKAssert(vkCurrentImageExtent.height() > 0); + + int ret = VK_SUCCESS; + LongBuffer attachments = stack.mallocLong(2); + LongBuffer pFramebuffer = stack.mallocLong(1); + + VkFramebufferCreateInfo framebufferInfo = VkFramebufferCreateInfo.callocStack(stack); + framebufferInfo.sType(VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO); + framebufferInfo.renderPass(hRenderPassHandle); + framebufferInfo.width(vkCurrentImageExtent.width()); + framebufferInfo.height(vkCurrentImageExtent.height()); + framebufferInfo.layers(1); + + framebufferHandles = new ArrayList<>(imageCount); + + for (var image : swapChainImages) { + attachments.put(0, image.GetImageViewHandle()); + attachments.put(1, cvkDepthImage.GetImageViewHandle()); + framebufferInfo.pAttachments(attachments); + ret = vkCreateFramebuffer(CVKDevice.GetVkDevice(), + framebufferInfo, + null, //allocation callbacks + pFramebuffer); + if (VkFailed(ret)) { return ret; } + framebufferHandles.add(pFramebuffer.get(0)); + } + + return ret; + } + + + /** + * Vulkan has explicit objects that represent render passes. It describes the + * frame buffer attachments such as the images in our swapchain, other depth, + * colour or stencil buffers. Subpasses read and write to these attachments. + * A render pass will be instanced for use in a command buffer. + * + * @param stack + * @return + */ + private int InitVKRenderPass(MemoryStack stack) { + CVKAssert(CVKDevice.GetVkDevice() != null); + CVKAssert(cvkCanvas.GetSurfaceFormat() != VK_FORMAT_NONE); + + int ret; + + // 0: colour, 1: depth + VkAttachmentDescription.Buffer attachments = VkAttachmentDescription.callocStack(2, stack); + VkAttachmentReference.Buffer attachmentRefs = VkAttachmentReference.callocStack(2, stack); + + // Colour attachment + VkAttachmentDescription colorAttachment = attachments.get(0); + colorAttachment.format(cvkCanvas.GetSurfaceFormat().Value()); + colorAttachment.samples(VK_SAMPLE_COUNT_1_BIT); + colorAttachment.loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR); + colorAttachment.storeOp(VK_ATTACHMENT_STORE_OP_STORE); + colorAttachment.stencilLoadOp(VK_ATTACHMENT_LOAD_OP_DONT_CARE); + colorAttachment.stencilStoreOp(VK_ATTACHMENT_STORE_OP_DONT_CARE); + + // These are the states of our display images at the start and end of this pass + colorAttachment.initialLayout(VK_IMAGE_LAYOUT_UNDEFINED); + colorAttachment.finalLayout(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + + VkAttachmentReference colorAttachmentRef = attachmentRefs.get(0); + colorAttachmentRef.attachment(0); + colorAttachmentRef.layout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + // Depth attachment + VkAttachmentDescription depthAttachment = attachments.get(1); + depthAttachment.format(depthFormat); + depthAttachment.samples(VK_SAMPLE_COUNT_1_BIT); + depthAttachment.loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR); + depthAttachment.storeOp(VK_ATTACHMENT_STORE_OP_DONT_CARE); + depthAttachment.stencilLoadOp(VK_ATTACHMENT_LOAD_OP_DONT_CARE); + depthAttachment.stencilStoreOp(VK_ATTACHMENT_STORE_OP_DONT_CARE); + depthAttachment.initialLayout(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);//VK_IMAGE_LAYOUT_UNDEFINED); + depthAttachment.finalLayout(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + + VkAttachmentReference depthAttachmentRef = attachmentRefs.get(1); + depthAttachmentRef.attachment(1); + depthAttachmentRef.layout(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + + VkSubpassDescription.Buffer subpass = VkSubpassDescription.callocStack(1, stack); + subpass.pipelineBindPoint(VK_PIPELINE_BIND_POINT_GRAPHICS); + subpass.colorAttachmentCount(1); + // This bit of hackery is because pColorAttachments is a buffer of multiple references, whereas pDepthStencilAttachment is singular + subpass.pColorAttachments(VkAttachmentReference.callocStack(1, stack).put(0, colorAttachmentRef)); + subpass.pDepthStencilAttachment(depthAttachmentRef); + + VkSubpassDependency.Buffer dependency = VkSubpassDependency.callocStack(1, stack); + dependency.srcSubpass(VK_SUBPASS_EXTERNAL); + dependency.dstSubpass(0); + dependency.srcStageMask(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); + dependency.srcAccessMask(0); + dependency.dstStageMask(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); + dependency.dstAccessMask(VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); + + VkRenderPassCreateInfo renderPassInfo = VkRenderPassCreateInfo.callocStack(stack); + renderPassInfo.sType(VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO); + renderPassInfo.pAttachments(attachments); + renderPassInfo.pSubpasses(subpass); + renderPassInfo.pDependencies(dependency); + + LongBuffer pRenderPass = stack.mallocLong(1); + ret = vkCreateRenderPass(CVKDevice.GetVkDevice(), + renderPassInfo, + null, //allocation callbacks + pRenderPass); + if (VkSucceeded(ret)) { + hRenderPassHandle = pRenderPass.get(0); + } + + return ret; + } + + /** + * This renderpass controls the rendering of the pick buffer. The use of a + * pick buffer is reason why Core Display Vulkan doesn’t need to render when + * the mouse moves. It just uses the mouse position to sample the pick + * buffer to determine what object if any we are over. Core Display OpenGL + * renders for every mouse move event (there are many even in a small + * movement) and resolves the mouse position on the fly. + * + * @param stack + * @return + */ + private int CreateOffscreenRenderPass(MemoryStack stack) { + CVKAssert(CVKDevice.GetVkDevice() != null); + CVKAssert(cvkCanvas.GetSurfaceFormat() != VK_FORMAT_NONE); + + int ret; + + // 0: colour, 1: depth + VkAttachmentDescription.Buffer attachments = VkAttachmentDescription.callocStack(2, stack); + VkAttachmentReference.Buffer attachmentRefs = VkAttachmentReference.callocStack(2, stack); + + // Colour attachment + VkAttachmentDescription colorAttachment = attachments.get(0); + colorAttachment.format(VK_FORMAT_R32_SFLOAT); + colorAttachment.samples(VK_SAMPLE_COUNT_1_BIT); + colorAttachment.loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR); + colorAttachment.storeOp(VK_ATTACHMENT_STORE_OP_STORE); + colorAttachment.stencilLoadOp(VK_ATTACHMENT_LOAD_OP_DONT_CARE); + colorAttachment.stencilStoreOp(VK_ATTACHMENT_STORE_OP_DONT_CARE); + + // These are the states of our display images at the start and end of this pass + colorAttachment.initialLayout(VK_IMAGE_LAYOUT_UNDEFINED); + // TODO - I think something is wrong with the colour attachment here + // VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL CONFIRM THIS + colorAttachment.finalLayout(VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + + VkAttachmentReference colorAttachmentRef = attachmentRefs.get(0); + colorAttachmentRef.attachment(0); + colorAttachmentRef.layout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + + // Depth attachment + VkAttachmentDescription depthAttachment = attachments.get(1); + depthAttachment.format(GetDepthFormat()); + depthAttachment.samples(VK_SAMPLE_COUNT_1_BIT); + depthAttachment.loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR); + depthAttachment.storeOp(VK_ATTACHMENT_STORE_OP_DONT_CARE); + depthAttachment.stencilLoadOp(VK_ATTACHMENT_LOAD_OP_DONT_CARE); + depthAttachment.stencilStoreOp(VK_ATTACHMENT_STORE_OP_DONT_CARE); + depthAttachment.initialLayout(VK_IMAGE_LAYOUT_UNDEFINED);//VK_IMAGE_LAYOUT_UNDEFINED); + depthAttachment.finalLayout(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + + VkAttachmentReference depthAttachmentRef = attachmentRefs.get(1); + depthAttachmentRef.attachment(1); + depthAttachmentRef.layout(VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + + VkSubpassDescription.Buffer subpass = VkSubpassDescription.callocStack(1, stack); + subpass.pipelineBindPoint(VK_PIPELINE_BIND_POINT_GRAPHICS); + subpass.colorAttachmentCount(1); + // This bit of hackery is because pColorAttachments is a buffer of multiple references, whereas pDepthStencilAttachment is singular + subpass.pColorAttachments(VkAttachmentReference.callocStack(1, stack).put(0, colorAttachmentRef)); + subpass.pDepthStencilAttachment(depthAttachmentRef); + + VkSubpassDependency.Buffer dependency = VkSubpassDependency.callocStack(1, stack); + dependency.srcSubpass(VK_SUBPASS_EXTERNAL); + dependency.dstSubpass(0); + dependency.srcStageMask(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); + dependency.srcAccessMask(0); + dependency.dstStageMask(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT); + dependency.dstAccessMask(VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); + + VkRenderPassCreateInfo renderPassInfo = VkRenderPassCreateInfo.callocStack(stack); + renderPassInfo.sType(VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO); + renderPassInfo.pAttachments(attachments); + renderPassInfo.pSubpasses(subpass); + renderPassInfo.pDependencies(dependency); + + LongBuffer pRenderPass = stack.mallocLong(1); + ret = vkCreateRenderPass(CVKDevice.GetVkDevice(), + renderPassInfo, + null, //allocation callbacks + pRenderPass); + if (VkSucceeded(ret)) { + hOffscreenRenderPassHandle = pRenderPass.get(0); + } + + return ret; + } + + /** + * Creates the primary command buffer for each image in the swapchain. + * A command buffer is a recorded list of commands to execute on the GPU. + * In OpenGL rendering commands are called immediately, this locks the CPU + * and GPU together during the execution of rendering commands. In Vulkan + * commands are recorded once then replayed each frame until changed. + * + * @param stack + * @return + */ + private int InitVKCommandBuffers(MemoryStack stack) { + CVKAssert(CVKDevice.GetVkDevice() != null); + CVKAssert(CVKDevice.GetCommandPoolHandle() != VK_NULL_HANDLE); + CVKAssert(imageCount > 0); + CVKAssert(vkCurrentImageExtent.width() > 0); + CVKAssert(vkCurrentImageExtent.height() > 0); + + int ret; + + commandBuffers = new ArrayList<>(imageCount); + + VkCommandBufferAllocateInfo allocInfo = VkCommandBufferAllocateInfo.callocStack(stack); + allocInfo.sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO); + allocInfo.commandPool(CVKDevice.GetCommandPoolHandle()); + allocInfo.level(VK_COMMAND_BUFFER_LEVEL_PRIMARY); + allocInfo.commandBufferCount(imageCount); + + PointerBuffer pCommandBuffers = stack.mallocPointer(imageCount); + ret = vkAllocateCommandBuffers(CVKDevice.GetVkDevice(), + allocInfo, + pCommandBuffers); + if (VkFailed(ret)) return ret; + + for (int i = 0; i < imageCount; ++i) { + commandBuffers.add(new VkCommandBuffer(pCommandBuffers.get(i), CVKDevice.GetVkDevice())); + } + + return ret; + } + + + public int WaitOnFence(int imageIndex) { + int ret; + ret = vkWaitForFences(CVKDevice.GetVkDevice(), + renderFenceHandles.get(imageIndex), + true, //wait on first or all fences, only one fence in this case + Long.MAX_VALUE); //timeout + if (VkFailed(ret)) return ret; + ret = vkResetFences(CVKDevice.GetVkDevice(), renderFenceHandles.get(imageIndex)); + return ret; + } + + + /** + * + * Get the first available image acquire semaphore and bind it to the image + * index returned.The image at imageIndex may still be in use by the + * presentation engine so the semaphore bound to this image must be used + * before executing commands into it. + * + * @param stack + * @param pImageIndex + * @param pImageAcquiredSemaphore + * @return + */ + public int AcquireNextImage(MemoryStack stack, IntBuffer pImageIndex, LongBuffer pImageAcquiredSemaphore) { + CVKAssert(CVKDevice.GetVkDevice() != null); + CVKAssert(GetSwapChainHandle() != VK_NULL_HANDLE); + CVKAssert(pImageIndex != null); + CVKAssert(pImageAcquiredSemaphore != null); + CVKAssert(imageAcquisitionHandles.size() > 0); + + // This semaphore is waited on by the command buffer queue submit. That means on successive calls + // to AcquireNextImage for the same image we return now, the semaphore is garaunteed to be in the + // signalled state. + long imageAcquiredSemaphore = imageAcquisitionHandles.get(nextImageAcquisitionIndex); + pImageAcquiredSemaphore.put(0, imageAcquiredSemaphore); + nextImageAcquisitionIndex = (++nextImageAcquisitionIndex) % imageCount; + + int ret; + ret = vkAcquireNextImageKHR(CVKDevice.GetVkDevice(), + hSwapChainHandle, + UINT64_MAX, //timeout + imageAcquiredSemaphore, + VK_NULL_HANDLE, + pImageIndex); + + return ret; + } + + + public int GetCommandBufferExecutedSemaphore(final int imageIndex, LongBuffer pCommandExecutionSemaphore) { + CVKAssert(imageIndex >= 0 && imageIndex < imageCount); + long hCommandExecutionSemaphore = commandExecutionHandles.get(imageIndex); + pCommandExecutionSemaphore.put(0, hCommandExecutionSemaphore); + return VK_SUCCESS; + } + + + private void DestroyVKCommandBuffers() { + try (MemoryStack stack = stackPush()) { + PointerBuffer pCommandBuffers = stack.mallocPointer(commandBuffers.size()); + for (int i = 0; i < imageCount; ++i) { + pCommandBuffers.put(commandBuffers.get(i).address()); + } + vkFreeCommandBuffers(CVKDevice.GetVkDevice(), CVKDevice.GetCommandPoolHandle(), pCommandBuffers); + commandBuffers.clear(); + GetLogger().info("Destroyed command buffers for all images"); + } + } + + private void DestroyVKFrameBuffer() { + for (int i = 0; i < imageCount; ++i) { + vkDestroyFramebuffer(CVKDevice.GetVkDevice(), framebufferHandles.get(i), null); + GetLogger().info("Destroyed frame buffer for image %d", i); + } + framebufferHandles.clear(); + } + + private void DestroyVKRenderPass() { + vkDestroyRenderPass(CVKDevice.GetVkDevice(), hRenderPassHandle, null); + hRenderPassHandle = VK_NULL_HANDLE; + GetLogger().info("Destroyed render pass"); + } + + private void DestroyVKOffscreenRenderPass() { + vkDestroyRenderPass(CVKDevice.GetVkDevice(), hOffscreenRenderPassHandle, null); + hOffscreenRenderPassHandle = VK_NULL_HANDLE; + GetLogger().info("Destroyed offscreenrender pass"); + } + + private void DestroyVKSwapChain() { + // Destroy synchronisation objects + for (int i = 0; i < imageCount; ++i) { + if (imageAcquisitionHandles.get(i) != VK_NULL_HANDLE) { + vkDestroySemaphore(CVKDevice.GetVkDevice(), imageAcquisitionHandles.get(i), null); + imageAcquisitionHandles.set(i, VK_NULL_HANDLE); + } + if (commandExecutionHandles.get(i) != VK_NULL_HANDLE) { + vkDestroySemaphore(CVKDevice.GetVkDevice(), commandExecutionHandles.get(i), null); + commandExecutionHandles.set(i, VK_NULL_HANDLE); + } + if (renderFenceHandles.get(i) != VK_NULL_HANDLE) { + vkDestroyFence(CVKDevice.GetVkDevice(), renderFenceHandles.get(i), null); + renderFenceHandles.set(i, VK_NULL_HANDLE); + } + GetLogger().info("Destroyed synchronisation objects for image %d", i); + } + imageAcquisitionHandles.clear(); + commandExecutionHandles.clear(); + renderFenceHandles.clear(); + + // The swapchain creates its own images but we create the image views for each one, destroy those now + for (int i = 0; i < imageCount; ++i) { + // Clear our list of images, we don't destroy these as the swapchain objects owns their memory + swapChainImages.get(i).Destroy(); + GetLogger().info("Destroyed image view for image %d", i); + } + + // Destroy the depth attachment + cvkDepthImage.Destroy(); + cvkDepthImage = null; + + // Finally, destroy the swapchain + vkDestroySwapchainKHR(CVKDevice.GetVkDevice(), hSwapChainHandle, null); + hSwapChainHandle = VK_NULL_HANDLE; + GetLogger().info("Destroyed swapchain"); + } + + + public boolean NeedsResize() { + checkVKret(cvkCanvas.UpdateSurfaceCapabilities()); + return vkCurrentImageExtent.width() != cvkCanvas.GetCurrentSurfaceExtent().width() + || vkCurrentImageExtent.height() != cvkCanvas.GetCurrentSurfaceExtent().height(); + } + + public VkExtent2D GetExtent() { return vkCurrentImageExtent; } + public int GetWidth() { return vkCurrentImageExtent.width(); } + public int GetHeight() { return vkCurrentImageExtent.height(); } + public int[] GetViewport(){ + return new int[]{0, GetHeight(), GetWidth(), -GetHeight()}; + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKVisualProcessor.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKVisualProcessor.java new file mode 100644 index 0000000000..477da9c8f5 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/CVKVisualProcessor.java @@ -0,0 +1,828 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan; + +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKIconTextureAtlas; +import au.gov.asd.tac.constellation.utilities.camera.Camera; +import au.gov.asd.tac.constellation.utilities.camera.Graphics3DUtilities; +import au.gov.asd.tac.constellation.utilities.graphics.Frustum; +import au.gov.asd.tac.constellation.utilities.graphics.Matrix44f; +import au.gov.asd.tac.constellation.utilities.visual.DirectionIndicatorsAction; +import au.gov.asd.tac.constellation.utilities.visual.DrawFlags; +import au.gov.asd.tac.constellation.utilities.visual.VisualAccess; +import au.gov.asd.tac.constellation.utilities.visual.VisualChange; +import au.gov.asd.tac.constellation.utilities.visual.VisualChangeBuilder; +import au.gov.asd.tac.constellation.utilities.visual.VisualManager; +import au.gov.asd.tac.constellation.utilities.visual.VisualOperation; +import au.gov.asd.tac.constellation.utilities.visual.VisualProcessor; +import au.gov.asd.tac.constellation.utilities.visual.VisualProcessor.VisualChangeProcessor; +import au.gov.asd.tac.constellation.utilities.visual.VisualProperty; +import au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKAxesRenderable; +import au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKBlazesRenderable; +import au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKFPSRenderable; +import au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKHitTester; +import au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKIconsRenderable; +import au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKIconLabelsRenderable; +import au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKLinkLabelsRenderable; +import au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKLinksRenderable; +import au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKLoopsRenderable; +import au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKNewLineRenderable; +import au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKPerspectiveLinksRenderable; +import au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKPointsRenderable; +import au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable; +import au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKSelectionBoxRenderable; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKGraphLogger; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_DEBUGGING; +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.Semaphore; +import static org.lwjgl.vulkan.VK10.VK_SUCCESS; + + +public class CVKVisualProcessor extends VisualProcessor { + + private static boolean DEBUGGING_POINTS = false; + + protected static final Cursor DEFAULT_CURSOR = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR); + protected static final Cursor CROSSHAIR_CURSOR = Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR); + + private static final float FIELD_OF_VIEW = 35; + private static final float PERSPECTIVE_NEAR = 1; + private static final float PERSPECTIVE_FAR = 500000; + + private final BlockingQueue taskQueue = new LinkedBlockingQueue<>(); + private DrawFlags drawFlags = DrawFlags.NONE; + private final CVKCanvas cvkCanvas; + private final Frustum viewFrustum = new Frustum(); + private final Matrix44f projectionMatrix = new Matrix44f(); + protected final CVKHitTester cvkHitTester; + private List hitTesters = new ArrayList<>(); + protected final CVKNewLineRenderable cvkNewLine; + protected final CVKSelectionBoxRenderable cvkSelectionBox; + private final CVKAxesRenderable cvkAxes; + private final CVKFPSRenderable cvkFPS; + private final CVKIconsRenderable cvkIcons; + private final CVKLinksRenderable cvkLinks; + private final CVKLoopsRenderable cvkLoops; + private final CVKPerspectiveLinksRenderable cvkPerspectiveLinks; + private final CVKBlazesRenderable cvkBlazes; + private final CVKIconLabelsRenderable cvkIconLabels; + private final CVKLinkLabelsRenderable cvkLinkLabels; + private final CVKPointsRenderable cvkPoints; + private final Matrix44f modelViewMatrix = new Matrix44f(); + private Camera camera = null; + private float pixelDensity = 0.0f; + private final Thread renderThread; + protected final CVKGraphLogger cvkLogger; + + // Copied from GraphRenderable, TODO: figure out what they are doing + private float motion = -1; + private long initialMotion; + + + // TODO: how should this work with continuous rendering? + // we need to somehow not cause the hit tester's NeedsDisplayUpdate to trigger + // a full redraw. + private boolean shouldRender = true; + + + public Matrix44f GetProjectionMatrix() { return projectionMatrix; } + public float GetPixelDensity() { return pixelDensity; } + public int GetFrameNumber() { return cvkCanvas != null ? cvkCanvas.GetFrameNumber() : -1; } + public Thread GetRenderThread() { return renderThread; } + public long GetRenderThreadID() { return renderThread != null ? renderThread.getId() : -1; } + public CVKGraphLogger GetLogger() { return cvkLogger; } + public CVKCanvas GetCanvas() { return cvkCanvas; } + public long GetPositionBufferViewHandle() { return cvkIcons.GetPositionBufferViewHandle(); } + public long GetPositionBufferHandle() { return cvkIcons.GetPositionBufferHandle(); } + public long GetPositionBufferSize() { return cvkIcons.GetPositionBufferSize(); } + public long GetVertexFlagsBufferViewHandle() { return cvkIcons.GetVertexFlagsBufferViewHandle(); } + public float GetMotion() { return motion; } + public CVKHitTester GetHitTester() { return cvkHitTester; } + public List GetHitTesterList() { return hitTesters; } + public DrawFlags GetDrawFlags() { return drawFlags; } + + + public CVKVisualProcessor(final String graphId) throws Throwable { + cvkLogger = new CVKGraphLogger(graphId); + try { + renderThread = Thread.currentThread(); + cvkLogger.info("Renderthread TID %d (java hash %d)", renderThread.getId(), renderThread.hashCode()); + + cvkCanvas = new CVKCanvas(this); + + // The order renderables is added is important as it dictates the + // render order. Labels render with a translucent background so + // must be rendered after links and icons to avoid needing to alpha + // sort. + cvkHitTester = new CVKHitTester(this); + cvkCanvas.AddRenderable(cvkHitTester); + cvkIcons = new CVKIconsRenderable(this); + cvkCanvas.AddRenderable(cvkIcons); + cvkLinks = new CVKLinksRenderable(this); + cvkCanvas.AddRenderable(cvkLinks); + cvkPerspectiveLinks = new CVKPerspectiveLinksRenderable(cvkLinks); + cvkCanvas.AddRenderable(cvkPerspectiveLinks); + cvkLoops = new CVKLoopsRenderable(this); + cvkCanvas.AddRenderable(cvkLoops); + cvkIconLabels = new CVKIconLabelsRenderable(this); + cvkCanvas.AddRenderable(cvkIconLabels); + cvkBlazes = new CVKBlazesRenderable(this); + cvkCanvas.AddRenderable(cvkBlazes); + cvkLinkLabels = new CVKLinkLabelsRenderable(this); + cvkCanvas.AddRenderable(cvkLinkLabels); + cvkNewLine = new CVKNewLineRenderable(this); + cvkCanvas.AddRenderable(cvkNewLine); + cvkSelectionBox = new CVKSelectionBoxRenderable(this); + cvkCanvas.AddRenderable(cvkSelectionBox); + cvkAxes = new CVKAxesRenderable(this); + cvkCanvas.AddRenderable(cvkAxes); + cvkPoints = new CVKPointsRenderable(this); + cvkCanvas.AddRenderable(cvkPoints); + cvkFPS = new CVKFPSRenderable(this); + cvkCanvas.AddRenderable(cvkFPS); + + hitTesters.add(cvkIcons); + hitTesters.add(cvkLinks); + hitTesters.add(cvkPerspectiveLinks); + hitTesters.add(cvkLoops); + + +// cvkIcons.DEBUG_skipRender = true; +// cvkLabels.DEBUG_skipRender = true; +// cvkNewLine.DEBUG_skipRender = true; +// cvkAxes.DEBUG_skipRender = true; +// cvkFPS.DEBUG_skipRender = true; + + } catch (Exception e) { + cvkLogger.LogException(e, "Exception raised constructing CVKVisualProcessor %s", graphId); + throw e; + } + } + + + public boolean IsRenderThreadCurrent() { + return GetRenderThread() == Thread.currentThread(); + } + + public boolean IsRenderThreadAlive() { + return renderThread != null ? renderThread.isAlive() : false; + } + + public void VerifyInRenderThread() { + if (CVK_DEBUGGING) { + if (!IsRenderThreadCurrent()) { + throw new RuntimeException(String.format("Error: render operation performed from thread %d, render thread %d", + Thread.currentThread().getId(), GetRenderThreadID())); + } + } + } + + public void RequestRedraw() { + shouldRender = true; + } + + protected void addTask(final CVKRenderUpdateTask task) { + shouldRender = true; + taskQueue.add(task); + } + + @Override + protected final void destroyCanvas() { + cvkCanvas.Destroy(); + } + + @Override + public VisualOperation exportToImage(final File imageFile) { + return new GLExportToImageOperation(imageFile); + } + + @Override + public VisualOperation exportToBufferedImage(final BufferedImage[] img1, final Semaphore waiter) { + return new GLExportToBufferedImageOperation(img1, waiter); + } + + /** + * Retrieve the model view projection matrix currently being used for + * visualisation. The projection matrix component is retrieved from the + * {@link GLRenderer}. + * + * @return The MVP matrix this visual processor is using in its current + * display cycle. + */ + public final Matrix44f getDisplayModelViewProjectionMatrix() { + Matrix44f mvpMatrix = new Matrix44f(); + mvpMatrix.multiply(projectionMatrix, modelViewMatrix); + return mvpMatrix; + } + + /** + * Get the camera currently being used for visualisation. + * + * @return The Camera this visual processor is using in its current display + * cycle. + */ + public final Camera getDisplayCamera() { + return camera; + } + + /** + * Sets the camera currently being used by this {@link VisualProcessor}. + *

+ * This is only used by the {@link GraphRenderable} to ensure that the + * Camera sent to the OpenGL context is in sync with the camera that can be + * retrieved here using {@link #getDisplayCamera getDisplayCamera()}. + * + * @param camera + */ + final void setDisplayCamera(final Camera camera) { + this.camera = camera; + } + + /** + * Get the model view matrix currently being used for visualisation. + * + * @return The MV matrix this visual processor is using in its current + * display cycle. + */ + public final Matrix44f getDisplayModelViewMatrix() { + return modelViewMatrix; // TODO: why is this different to Graphics3DUtilities.getModelViewMatrix(camera)? + } + + /** + * Get the model view projection matrix corresponding to the supplied + * camera. The projection matrix component is retrieved from the + * {@link GLRenderer}. + * + * @param camera The {@link Camera} from which to calculate the model view + * component of the matrix. + * @return The MVP matrix corresponding to the supplied camera and the + * current projection of the {@link GLRenderer}. + */ + protected Matrix44f getCameraModelViewProjectionMatrix(final Camera camera) { + final Matrix44f mvMatrix = Graphics3DUtilities.getModelViewMatrix(camera); + final Matrix44f mvpMatrix = new Matrix44f(); + mvpMatrix.multiply(projectionMatrix, mvMatrix); + return mvpMatrix; + } + + /** + * Get the viewport (height, width, x, y) currently in use by the + * {@link GLRenderer}. + * + * @return The viewport from the {@link GLRenderer}. + */ + protected final int[] getViewport() { + return cvkCanvas.GetRenderer().getViewport(); + } + + @Override + public final void performVisualUpdate() { + // performVisualUpdate maybe called before the JPanel is added to its + // parent. We can't get a renderable surface until the parent chain is + // intact. + + // Direction Indicators. + if (motion == -1) { + if (DirectionIndicatorsAction.isShowIndicators()) { + initialMotion = System.currentTimeMillis(); + motion = 0; + } + } else if (DirectionIndicatorsAction.isShowIndicators()) { + motion = (System.currentTimeMillis() - initialMotion) / 100f; + } else { + motion = -1; + } + + // If we are currently rendering the newLine to indicate where a connection will + // will go then we must repaint. + if (cvkNewLine.GetVertexCount() > 0) { + shouldRender = true; + } + + if (shouldRender || VisualManager.ContinuoslyRedraw()) { + cvkCanvas.repaint(); + shouldRender = false; + } else { + cvkHitTester.ServiceHitRequests(); + signalProcessorIdle(); + } + } + + @Override + protected void initialise() { + } + + @Override + protected void cleanup() { + // No need to destroy the canvas as there will be another call from the + // visual manager to explicity destroy it. + //cvkCanvas.Destroy(); + } + + private final class GLExportToImageOperation implements VisualOperation { + + private final File file; + + public GLExportToImageOperation(final File file) { + this.file = file; + } + + @Override + public void apply() { + + // Currently in the VisualProcessor thread, add a task to trigger + // the save to file from the Render thread. + if (cvkCanvas.GetRenderer() != null) { + addTask(cvkCanvas.GetRenderer().TaskRequestScreenshot(file)); + } +// graphRenderable.addTask(drawable -> { +// final GL30 gl = drawable.getGL().getGL3(); +// gl.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, 0); +// final AWTGLReadBufferUtil util = new AWTGLReadBufferUtil(drawable.getGLProfile(), false); +// BufferedImage img = util.readPixelsToBufferedImage(gl, true); +// // Write the image out as a PNG. +// try { +// ImageIO.write(img, "png", file); +// } catch (IOException ex) { +// GetLogger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex); +// } +// }); + } + + @Override + public List getVisualChanges() { + return Arrays.asList(new VisualChangeBuilder(VisualProperty.EXTERNAL_CHANGE).build()); + } + } + + private final class GLExportToBufferedImageOperation implements VisualOperation { + + private final BufferedImage[] img1; + private final Semaphore waiter; + + /** + * Export the current GL display to a BufferedImage. + *

+ * The VisualProcessor paradigm doesn't lend itself to returning data, + * due to the asynchronous operation queue. Therefore, a single element + * array is passed in and a reference to the newly created BufferedImage + * is assigned at index 0. The caller maintains a reference to this + * array to access the BufferedImage. (An array is used to avoid + * creating yet another class with only one property.) + *

+ * Because the operation is asynchronous, the caller needs to know when + * the BufferedImage is ready. A Semaphore with zero permits is passed + * in. The operation releases a permit after the BufferedImage is + * assigned. The caller waits on the Semaphore and proceeds when a + * permit is acquired. + * + * @param img1 A single element array; the new BufferedImage is assigned + * to index 0. + * @param waiter A Semaphore with no permits available; a permit is + * released when the BufferedImage has been assigned. + */ + public GLExportToBufferedImageOperation(final BufferedImage[] img1, final Semaphore waiter) { + this.img1 = img1; + this.waiter = waiter; + } + + @Override + public void apply() { + // TODO: this whole func +// graphRenderable.addTask(drawable -> { +// final GL30 gl = drawable.getGL().getGL3(); +// gl.glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, 0); +// final AWTGLReadBufferUtil util = new AWTGLReadBufferUtil(drawable.getGLProfile(), false); +// img1[0] = util.readPixelsToBufferedImage(gl, true); +// +// waiter.release(); +// }); + } + + @Override + public List getVisualChanges() { + return Arrays.asList(new VisualChangeBuilder(VisualProperty.EXTERNAL_CHANGE).build()); + } + } + + /** + * This exists so CVKRenderer can call it the wrapped function that would + * otherwise have private exposure to it. + */ + @Override + protected void signalProcessorIdle() { + super.signalProcessorIdle(); + } + + /** + * This exists so CVKRenderer can call it the wrapped function that would + * otherwise have private exposure to it. + */ + @Override + protected void requestRedraw() { + super.requestRedraw(); + } + + @Override + protected void rebuild() { + super.rebuild(); + } + + @Override + protected Component getCanvas() { + return cvkCanvas; + } + + public Rectangle getCanvasBounds() { + return cvkCanvas.getBounds(); + } + + /** + * All the double negatives + * + * @return + */ + public boolean surfaceReady() { + return (cvkCanvas != null) ? !cvkCanvas.getBounds().isEmpty() : false; + } + + public int ProcessRenderTasks(CVKSwapChain cvkSwapChain) { + int ret = VK_SUCCESS; + final List tasks = new ArrayList<>(); + taskQueue.drainTo(tasks); + tasks.forEach(task -> { task.run(); }); + return ret; + } + + @Override + public List getFullRefreshSet(final VisualAccess access) { + return Arrays.asList( + new VisualChangeBuilder(VisualProperty.VERTICES_REBUILD).build(), + new VisualChangeBuilder(VisualProperty.CONNECTIONS_REBUILD).build(), + new VisualChangeBuilder(VisualProperty.BACKGROUND_COLOR).forItems(1).build(), + new VisualChangeBuilder(VisualProperty.HIGHLIGHT_COLOUR).forItems(1).build(), + new VisualChangeBuilder(VisualProperty.CONNECTIONS_OPACITY).forItems(1).build(), + new VisualChangeBuilder(VisualProperty.BLAZE_SIZE).forItems(1).build(), + // new VisualChangeBuilder(VisualProperty.DRAW_FLAGS).forItems(1).build(), + new VisualChangeBuilder(VisualProperty.CAMERA).forItems(1).build() + ); + } + + @Override + protected final Set getTrumpedProperties(final VisualProperty property) { + switch (property) { + case VERTICES_REBUILD: + return new HashSet<>(Arrays.asList( + VisualProperty.VERTEX_SELECTED, VisualProperty.VERTEX_X, + VisualProperty.VERTEX_COLOR, VisualProperty.VERTEX_FOREGROUND_ICON, + VisualProperty.VERTEX_BLAZED, VisualProperty.BOTTOM_LABELS_REBUILD, + VisualProperty.TOP_LABELS_REBUILD, VisualProperty.DRAW_FLAGS + )); + case CONNECTIONS_REBUILD: + return new HashSet<>(Arrays.asList( + VisualProperty.CONNECTION_SELECTED, VisualProperty.CONNECTION_COLOR, + VisualProperty.CONNECTION_LABELS_REBUILD + )); + case CONNECTION_LABELS_REBUILD: + return new HashSet<>(Arrays.asList( + VisualProperty.CONNECTION_LABEL_COLOR + )); + case TOP_LABELS_REBUILD: + return new HashSet<>(Arrays.asList( + VisualProperty.TOP_LABEL_COLOR + )); + case BOTTOM_LABELS_REBUILD: + return new HashSet<>(Arrays.asList( + VisualProperty.BOTTOM_LABEL_COLOR + )); + default: + return super.getTrumpedProperties(property); + } + } + + @Override + protected final VisualProperty getMasterProperty(final VisualProperty property) { + + switch (property) { + case BLAZE_SIZE: + case BLAZE_OPACITY: + return VisualProperty.BLAZE_SIZE; + case VISIBLE_ABOVE_THRESHOLD: + case VISIBILITY_THRESHOLD: + return VisualProperty.DRAW_FLAGS; + case VERTEX_SELECTED: + case VERTEX_DIM: + return VisualProperty.VERTEX_SELECTED; + case VERTEX_RADIUS: + case VERTEX_X: + case VERTEX_Y: + case VERTEX_Z: + case VERTEX_X2: + case VERTEX_Y2: + case VERTEX_Z2: + return VisualProperty.VERTEX_X; + case VERTEX_COLOR: + return VisualProperty.VERTEX_COLOR; + case VERTEX_FOREGROUND_ICON: + case VERTEX_BACKGROUND_ICON: + case VERTEX_NW_DECORATOR: + case VERTEX_NE_DECORATOR: + case VERTEX_SW_DECORATOR: + case VERTEX_SE_DECORATOR: + return VisualProperty.VERTEX_FOREGROUND_ICON; + case VERTEX_BLAZED: + case VERTEX_BLAZE_ANGLE: + case VERTEX_BLAZE_COLOR: + return VisualProperty.VERTEX_BLAZED; + case VERTEX_VISIBILITY: + case VERTICES_ADDED: + case VERTICES_REMOVED: + case VERTICES_REBUILD: + return VisualProperty.VERTICES_REBUILD; + case CONNECTION_COLOR: + return VisualProperty.CONNECTION_COLOR; + case CONNECTION_SELECTED: + case CONNECTION_DIRECTED: + case CONNECTION_DIM: + case CONNECTION_LINESTYLE: + return VisualProperty.CONNECTION_SELECTED; + case CONNECTION_VISIBILITY: + case CONNECTION_WIDTH: + case CONNECTIONS_ADDED: + case CONNECTIONS_REMOVED: + case CONNECTIONS_REBUILD: + return VisualProperty.CONNECTIONS_REBUILD; + case TOP_LABEL_SIZE: + case TOP_LABELS_REBUILD: + case TOP_LABEL_TEXT: + return VisualProperty.TOP_LABELS_REBUILD; + case BOTTOM_LABEL_SIZE: + case BOTTOM_LABELS_REBUILD: + case BOTTOM_LABEL_TEXT: + return VisualProperty.BOTTOM_LABELS_REBUILD; + case CONNECTION_LABEL_SIZE: + case CONNECTION_LABELS_REBUILD: + case CONNECTION_LABEL_TEXT: + return VisualProperty.CONNECTION_LABELS_REBUILD; + default: + return super.getMasterProperty(property); + } + + } + + @Override + protected final VisualChangeProcessor getChangeProcessor(final VisualProperty property) { + if (DEBUGGING_POINTS) { + switch (property) { + case VERTICES_REBUILD: + return (change, access) -> { + if (cvkPoints != null) { + addTask(cvkPoints.TaskRebuildPoints(access)); + } + }; + case VERTEX_X: + return (change, access) -> { + if (cvkPoints != null) { + addTask(cvkPoints.TaskUpdatePoints(change, access)); + } + }; + case CAMERA: + return (change, access) -> { + camera = access.getCamera(); + setDisplayCamera(camera); + Graphics3DUtilities.getModelViewMatrix(camera.lookAtEye, camera.lookAtCentre, camera.lookAtUp, getDisplayModelViewMatrix()); + + if (cvkAxes != null) { + addTask(cvkAxes.TaskUpdateCamera()); + } + if (cvkPoints != null) { + addTask(cvkPoints.TaskUpdateCamera()); + } + }; + case EXTERNAL_CHANGE: + default: + return (change, access) -> { + }; + } + } + + + try { + switch (property) { + case VERTICES_REBUILD: + return (change, access) -> { + // Why are we not getting a CAMERA change on some graphs? + if (camera == null && access.getCamera() != null) { + camera = access.getCamera(); + setDisplayCamera(camera); + Graphics3DUtilities.getModelViewMatrix(camera.lookAtEye, camera.lookAtCentre, camera.lookAtUp, getDisplayModelViewMatrix()); + } + + addTask(cvkIcons.TaskUpdateIcons(change, access)); + addTask(cvkIcons.TaskUpdatePositions(change, access)); + addTask(cvkIcons.TaskUpdateVertexFlags(change, access)); + + addTask(cvkIconLabels.TaskUpdateLabels(change, access)); + addTask(cvkIconLabels.TaskUpdateColours(change, access)); + addTask(cvkIconLabels.TaskUpdateSizes(change, access)); + + addTask(cvkBlazes.TaskUpdateBlazes(change, access)); + + // Visibility flags to control what is drawn + final DrawFlags updatedDrawFlags = access.getDrawFlags(); + addTask(() -> { drawFlags = updatedDrawFlags; }); + }; + case CONNECTIONS_REBUILD: + return (change, access) -> { + addTask(cvkLinks.TaskUpdateLinks(change, access)); + addTask(cvkLinkLabels.TaskUpdateSizes(change, access)); + addTask(cvkLinkLabels.TaskUpdateColours(change, access)); + addTask(cvkLinkLabels.TaskUpdateLabels(change, access)); + addTask(cvkLoops.TaskUpdateLoops(change, access)); + }; + case BACKGROUND_COLOR: + return (change, access) -> { + if (cvkCanvas.GetRenderer() != null) { + addTask(cvkCanvas.GetRenderer().BackgroundColourChanged(change, access)); + } + addTask(cvkLinkLabels.TaskSetBackgroundColor(access)); + addTask(cvkIconLabels.TaskSetBackgroundColor(access)); + }; + case HIGHLIGHT_COLOUR: + return (change, access) -> { + addTask(cvkIcons.TaskSetHighlightColour(access)); + addTask(cvkIconLabels.TaskSetHighlightColor(access)); + addTask(cvkLinkLabels.TaskSetHighlightColor(access)); + addTask(cvkLinks.TaskSetHighlightColour(access)); + }; + case DRAW_FLAGS: + return (change, access) -> { + final DrawFlags updatedDrawFlags = access.getDrawFlags(); + addTask(() -> { drawFlags = updatedDrawFlags; }); + }; + case BLAZE_SIZE: + return (change, access) -> { + addTask(cvkBlazes.TaskUpdateSizeAndOpacity(access)); + }; + case CONNECTIONS_OPACITY: + return (change, access) -> { + addTask(cvkLinks.TaskUpdateOpacity(access)); + }; + case BOTTOM_LABEL_COLOR: + return (change, access) -> { + addTask(cvkIconLabels.TaskUpdateColours(change, access)); + }; + case BOTTOM_LABELS_REBUILD: + return (change, access) -> { + addTask(cvkIconLabels.TaskUpdateLabels(change, access)); + addTask(cvkIconLabels.TaskUpdateColours(change, access)); + addTask(cvkIconLabels.TaskUpdateSizes(change, access)); + }; + case CAMERA: + return (change, access) -> { + camera = access.getCamera(); + setDisplayCamera(camera); + Graphics3DUtilities.getModelViewMatrix(camera.lookAtEye, camera.lookAtCentre, camera.lookAtUp, getDisplayModelViewMatrix()); + + if (cvkAxes != null) { + addTask(cvkAxes.TaskUpdateCamera()); + } + addTask(cvkIcons.TaskUpdateCamera()); + addTask(cvkIconLabels.TaskUpdateCamera()); + addTask(cvkLinkLabels.TaskUpdateCamera()); + addTask(cvkLinks.TaskUpdateCamera()); + addTask(cvkLoops.TaskUpdateCamera()); + addTask(cvkBlazes.TaskUpdateCamera()); + }; + case CONNECTION_LABEL_COLOR: + return (change, access) -> { + addTask(cvkLinkLabels.TaskUpdateColours(change, access)); + }; + case CONNECTION_LABELS_REBUILD: + return (change, access) -> { + addTask(cvkLinkLabels.TaskUpdateSizes(change, access)); + addTask(cvkLinkLabels.TaskUpdateColours(change, access)); + addTask(cvkLinkLabels.TaskUpdateLabels(change, access)); + }; + case TOP_LABEL_COLOR: + return (change, access) -> { + addTask(cvkIconLabels.TaskUpdateColours(change, access)); + }; + case TOP_LABELS_REBUILD: + return (change, access) -> { + addTask(cvkIconLabels.TaskUpdateLabels(change, access)); + addTask(cvkIconLabels.TaskUpdateColours(change, access)); + addTask(cvkIconLabels.TaskUpdateSizes(change, access)); + }; + case CONNECTION_COLOR: + return (change, access) -> { + addTask(cvkLinks.TaskUpdateLinks(change, access)); + addTask(cvkLoops.TaskUpdateLoops(change, access)); + }; + case CONNECTION_SELECTED: + return (change, access) -> { + addTask(cvkLinks.TaskUpdateLinks(change, access)); + addTask(cvkLoops.TaskUpdateLoops(change, access)); + }; + case VERTEX_BLAZED: + GetLogger().warning("Event VERTEX_BLAZED unhandled!"); + return (change, access) -> { + // Note that updating blazes always rebuilds from scratch, so it is not an issue if the batch was not 'ready'. + addTask(cvkBlazes.TaskUpdateBlazes(change, access)); + }; + case VERTEX_COLOR: + return (change, access) -> { + if (cvkIcons != null) { + addTask(cvkIcons.TaskUpdateColours(change, access)); + } + }; + case VERTEX_FOREGROUND_ICON: + return (change, access) -> { + addTask(cvkIcons.TaskUpdateIcons(change, access)); + }; + + case VERTEX_SELECTED: + return (change, access) -> { + addTask(cvkIcons.TaskUpdateVertexFlags(change, access)); + }; + case VERTEX_X: + return (change, access) -> { + addTask(cvkIcons.TaskUpdatePositions(change, access)); + }; + case EXTERNAL_CHANGE: + default: + return (change, access) -> { + }; + } + } catch (Exception e) { + cvkLogger.LogException(e, "Exception thrown processing visual change %s:", property); + throw e; + } + } + + public void SwapChainRecreated(CVKSwapChain cvkSwapChain) { + // Create the projection matrix, and load it on the projection matrix stack. + viewFrustum.setPerspective(FIELD_OF_VIEW, (float)cvkSwapChain.GetWidth() / (float)cvkSwapChain.GetHeight(), PERSPECTIVE_NEAR, PERSPECTIVE_FAR); + pixelDensity = (float)(cvkSwapChain.GetHeight() * 0.5 / Math.tan(Math.toRadians(FIELD_OF_VIEW))); + projectionMatrix.set(viewFrustum.getProjectionMatrix()); + + // Compared to OpenGL Vulkan has a RHS with Y pointing down and it also + // calculates the Z differently in clipspace. The Y needs to be flipped + // screenspace in whatever shader applies the projection matrix. The + // clipspace Z calculation can either be applied in the shader at the same + // time by averaging gl_position z and w after the projection or it can + // be applied to the projection matrix. The latter is cheaper as it we + // do it once for the per scene projection matrix rather than having to + // do it per vertex for every renderable. + // + // NOTE: this appears not to be needed. Leaving this note here for now + // until there is time to analyse the required linear algebra with + // RenderDoc. + // + // Reference: https://matthewwellings.com/blog/the-new-vulkan-coordinate-system/ +// Matrix44f pre = new Matrix44f(); +// pre.makeIdentity(); +// pre.set(1, 1, -1); +// pre.set(2, 2, 0.5f); +// pre.set(2, 3, 0.5f); +// projectionMatrix.multiply(pre, viewFrustum.getProjectionMatrix()); + } + + + /** + * This is triggered by the ExportIconTextureAtlasAction from the developer menu to + * dump the icon atlas to disk for debugging. + * + * @param file + */ + public static void ExportIconTextureAtlas(File file) { + if (CVKIconTextureAtlas.IsInstantiated()) { + CVKRenderUpdateTask task = CVKIconTextureAtlas.GetInstance().TaskSaveToFile(file); + task.run(); + } + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKAxesRenderable.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKAxesRenderable.java new file mode 100644 index 0000000000..b96d113b97 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKAxesRenderable.java @@ -0,0 +1,621 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.renderables; + +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.*; +import static org.lwjgl.system.MemoryUtil.*; +import static org.lwjgl.vulkan.VK10.*; +import static org.lwjgl.system.MemoryStack.stackPush; +import au.gov.asd.tac.constellation.utilities.camera.Graphics3DUtilities; +import au.gov.asd.tac.constellation.utilities.graphics.Matrix44f; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import java.nio.ByteBuffer; +import java.nio.LongBuffer; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkPipelineLayoutCreateInfo; +import org.lwjgl.vulkan.VkCommandBufferInheritanceInfo; +import org.lwjgl.vulkan.VkVertexInputAttributeDescription; +import org.lwjgl.vulkan.VkVertexInputBindingDescription; +import au.gov.asd.tac.constellation.utilities.graphics.Vector3f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector4f; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool.CVKDescriptorPoolRequirements; +import au.gov.asd.tac.constellation.visual.vulkan.CVKRenderUpdateTask; +import au.gov.asd.tac.constellation.visual.vulkan.CVKVisualProcessor; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_CLEAN; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_NEEDS_REBUILD; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKBuffer; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKCommandBuffer; +import java.util.ArrayList; +import org.lwjgl.vulkan.VkPushConstantRange; + + +public class CVKAxesRenderable extends CVKRenderable { + // FROM AxesRenderable... + private static final float LEN = 0.5f; + private static final float HEAD = 0.05f; + private static final int AXES_OFFSET = 50; + private static final Vector4f XCOLOR = new Vector4f(1, 0.5f, 0.5f, 0.75f); + private static final Vector4f YCOLOR = new Vector4f(0.5f, 1, 0.5f, 0.75f); + private static final Vector4f ZCOLOR = new Vector4f(0, 0.5f, 1, 0.75f); + + + // All the verts are manually calculated for the Axes in CreateVertexBuffer(): + // - (3 x 2) = X, Y, Z lines for Axes + // - (4, 4, 4) = Arrows at the end of the Axes + // - (4, 6, 6) = X, Y, Z labels + private static final int NUMBER_OF_VERTICES = 3 * 2 + 4 + 4 + 4 + 4 + 6 + 6; + + private final Vector3f topRightCorner = new Vector3f(); + private float pScale = 0; + + private final Vertex[] vertices = new Vertex[NUMBER_OF_VERTICES]; + private CVKBuffer cvkVertexBuffer = null; + private CVKCommandBuffer cvkDisplayCommandBuffer; + private ByteBuffer pushConstants = null; + + + // ========================> Classes <======================== \\ + + private static class Vertex { + + private static final int BYTES = Vector3f.BYTES + Vector4f.BYTES; + private static final int OFFSETOF_POS = 0; + private static final int OFFSETOF_COLOR = Vector3f.BYTES; + + private final Vector3f vertex; + private final Vector4f color; + + public Vertex(final Vector3f vertex, final Vector4f color) { + this.vertex = vertex; + this.color = color; + } + + private static void CopyTo(ByteBuffer buffer, Vertex[] vertices) { + for(Vertex vertex : vertices) { + buffer.putFloat(vertex.vertex.getX()); + buffer.putFloat(vertex.vertex.getY()); + buffer.putFloat(vertex.vertex.getZ()); + + buffer.putFloat(vertex.color.a[0]); + buffer.putFloat(vertex.color.a[1]); + buffer.putFloat(vertex.color.a[2]); + buffer.putFloat(vertex.color.a[3]); + } + } + + private static VkVertexInputBindingDescription.Buffer GetBindingDescription() { + + VkVertexInputBindingDescription.Buffer bindingDescription = + VkVertexInputBindingDescription.callocStack(1); + + bindingDescription.binding(0); + bindingDescription.stride(Vertex.BYTES); + bindingDescription.inputRate(VK_VERTEX_INPUT_RATE_VERTEX); + + return bindingDescription; + } + + private static VkVertexInputAttributeDescription.Buffer GetAttributeDescriptions() { + + VkVertexInputAttributeDescription.Buffer attributeDescriptions = + VkVertexInputAttributeDescription.callocStack(2); + + // Vertex + VkVertexInputAttributeDescription vertexDescription = attributeDescriptions.get(0); + vertexDescription.binding(0); + vertexDescription.location(0); + vertexDescription.format(VK_FORMAT_R32G32B32_SFLOAT); + vertexDescription.offset(OFFSETOF_POS); + + // Color + VkVertexInputAttributeDescription colorDescription = attributeDescriptions.get(1); + colorDescription.binding(0); + colorDescription.location(1); + colorDescription.format(VK_FORMAT_R32G32B32A32_SFLOAT); + colorDescription.offset(OFFSETOF_COLOR); + + return attributeDescriptions.rewind(); + } + } + + @Override + protected VkVertexInputBindingDescription.Buffer GetVertexBindingDescription() { + return Vertex.GetBindingDescription(); + } + + @Override + protected VkVertexInputAttributeDescription.Buffer GetVertexAttributeDescriptions() { + return Vertex.GetAttributeDescriptions(); + } + + + // ========================> Shaders <======================== \\ + + @Override + protected String GetVertexShaderName() { return "PassThru.vs"; } + + @Override + protected String GetGeometryShaderName() { return "PassThruLine.gs"; } + + @Override + protected String GetFragmentShaderName() { return "PassThru.fs"; } + + + // ========================> Lifetime <======================== \\ + + public CVKAxesRenderable(CVKVisualProcessor visualProcessor) { + super(visualProcessor); + colourBlend = false; + depthTest = false; + depthWrite = false; + assemblyTopology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + } + + @Override + public int Initialise() { + int ret = super.Initialise(); + if (VkFailed(ret)) { return ret; } + + // Initialise push constants to identity mtx + CreatePushConstants(); + + ret = CreatePipelineLayout(); + if (VkFailed(ret)) { return ret; } + + return VK_SUCCESS; + } + + @Override + public void Destroy() { + DestroyCommandBuffer(); + DestroyVertexBuffer(); + DestroyPipelines(); + DestroyPipelineLayout(); + DestroyPushConstants(); + + CVKAssertNull(displayPipelines); + CVKAssertNull(hPipelineLayout); + CVKAssertNull(cvkVertexBuffer); + CVKAssertNull(cvkDisplayCommandBuffer); + CVKAssertNull(pushConstants); + } + + + // ========================> Swap chain <======================== \\ + + @Override + protected int DestroySwapChainResources(){ + cvkVisualProcessor.VerifyInRenderThread(); + int ret = VK_SUCCESS; + + // We only need to recreate these resources if the number of images in + // the swapchain changes or if this is the first call after the initial + // swapchain is created. + if (displayPipelines != null && swapChainImageCountChanged) { + DestroyVertexBuffer(); + DestroyCommandBuffer(); + DestroyPipelines(); + DestroyCommandBuffer(); + + CVKAssertNull(displayPipelines); + CVKAssertNull(cvkVertexBuffer); + CVKAssertNull(cvkDisplayCommandBuffer); + } + + return ret; + } + + + // ========================> Vertex buffers <======================== \\ + + private int CreateVertexBuffer(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + int ret = VK_SUCCESS; + + // Size to upper limit, we don't have to draw each one. + int size = vertices.length * Vertex.BYTES; + + // Converted from AxesRenderable.java. Keeping comments for reference. + int i = 0; + // x axis + // axesBatch.stage(colorTarget, XCOLOR); + // axesBatch.stage(vertexTarget, ZERO_3F); + vertices[i++] = new Vertex(ZERO_3F, XCOLOR); + // axesBatch.stage(colorTarget, XCOLOR); + // axesBatch.stage(vertexTarget, LEN, 0, 0); + vertices[i++] = new Vertex(new Vector3f(LEN,0f,0f), XCOLOR); + + // arrow + // axesBatch.stage(colorTarget, XCOLOR); + // axesBatch.stage(vertexTarget, LEN - HEAD, HEAD, 0); + vertices[i++] = new Vertex(new Vector3f(LEN - HEAD, HEAD, 0f), XCOLOR); + // axesBatch.stage(colorTarget, XCOLOR); + // axesBatch.stage(vertexTarget, LEN, 0, 0); + vertices[i++] = new Vertex(new Vector3f(LEN, 0f, 0f), XCOLOR); + // axesBatch.stage(colorTarget, XCOLOR); + // axesBatch.stage(vertexTarget, LEN, 0, 0); + vertices[i++] = new Vertex(new Vector3f(LEN, 0f, 0f), XCOLOR); + // axesBatch.stage(colorTarget, XCOLOR); + // axesBatch.stage(vertexTarget, LEN - HEAD, -HEAD, 0); + vertices[i++] = new Vertex(new Vector3f(LEN - HEAD, -HEAD, 0f), XCOLOR); + + // X + // axesBatch.stage(colorTarget, XCOLOR); + // axesBatch.stage(vertexTarget, LEN + HEAD, HEAD, HEAD); + vertices[i++] = new Vertex(new Vector3f(LEN + HEAD, HEAD, HEAD), XCOLOR); + // axesBatch.stage(colorTarget, XCOLOR); + // axesBatch.stage(vertexTarget, LEN + HEAD, -HEAD, -HEAD); + vertices[i++] = new Vertex(new Vector3f(LEN + HEAD, -HEAD, -HEAD), XCOLOR); + // axesBatch.stage(colorTarget, XCOLOR); + // axesBatch.stage(vertexTarget, LEN + HEAD, HEAD, -HEAD); + vertices[i++] = new Vertex(new Vector3f(LEN + HEAD, HEAD, -HEAD), XCOLOR); + // axesBatch.stage(colorTarget, XCOLOR); + // axesBatch.stage(vertexTarget, LEN + HEAD, -HEAD, HEAD); + vertices[i++] = new Vertex(new Vector3f(LEN + HEAD, -HEAD, HEAD), XCOLOR); + + // y axis + // axesBatch.stage(colorTarget, YCOLOR); + // axesBatch.stage(vertexTarget, ZERO_3F); + vertices[i++] = new Vertex(ZERO_3F, YCOLOR); + // axesBatch.stage(colorTarget, YCOLOR); + // axesBatch.stage(vertexTarget, 0, LEN, 0); + vertices[i++] = new Vertex(new Vector3f(0f, LEN, 0f), YCOLOR); + // arrow + // axesBatch.stage(colorTarget, YCOLOR); + // axesBatch.stage(vertexTarget, 0, LEN - HEAD, HEAD); + vertices[i++] = new Vertex(new Vector3f(0f, LEN - HEAD, HEAD), YCOLOR); + // axesBatch.stage(colorTarget, YCOLOR); + // axesBatch.stage(vertexTarget, 0, LEN, 0); + vertices[i++] = new Vertex(new Vector3f(0f, LEN, 0f), YCOLOR); + // axesBatch.stage(colorTarget, YCOLOR); + // axesBatch.stage(vertexTarget, 0, LEN, 0); + vertices[i++] = new Vertex(new Vector3f(0f, LEN, 0f), YCOLOR); + // axesBatch.stage(colorTarget, YCOLOR); + // axesBatch.stage(vertexTarget, 0, LEN - HEAD, -HEAD); + vertices[i++] = new Vertex(new Vector3f(0f, LEN - HEAD, -HEAD), YCOLOR); + // Y + // axesBatch.stage(colorTarget, YCOLOR); + // axesBatch.stage(vertexTarget, -HEAD, LEN + HEAD, -HEAD); + vertices[i++] = new Vertex(new Vector3f(-HEAD, LEN + HEAD, -HEAD), YCOLOR); + // axesBatch.stage(colorTarget, YCOLOR); + // axesBatch.stage(vertexTarget, 0, LEN + HEAD, 0); + vertices[i++] = new Vertex(new Vector3f(0f, LEN + HEAD, 0f), YCOLOR); + // axesBatch.stage(colorTarget, YCOLOR); + // axesBatch.stage(vertexTarget, HEAD, LEN + HEAD, -HEAD); + vertices[i++] = new Vertex(new Vector3f(HEAD, LEN + HEAD, -HEAD), YCOLOR); + // axesBatch.stage(colorTarget, YCOLOR); + // axesBatch.stage(vertexTarget, 0, LEN + HEAD, 0); + vertices[i++] = new Vertex(new Vector3f(0f, LEN + HEAD, 0f), YCOLOR); + // axesBatch.stage(colorTarget, YCOLOR); + // axesBatch.stage(vertexTarget, 0, LEN + HEAD, 0); + vertices[i++] = new Vertex(new Vector3f(0f, LEN + HEAD, 0f), YCOLOR); + // axesBatch.stage(colorTarget, YCOLOR); + // axesBatch.stage(vertexTarget, 0, LEN + HEAD, HEAD); + vertices[i++] = new Vertex(new Vector3f(0f, LEN + HEAD, HEAD), YCOLOR); + + // z axis + // axesBatch.stage(colorTarget, ZCOLOR); + // axesBatch.stage(vertexTarget, ZERO_3F); + vertices[i++] = new Vertex(ZERO_3F, ZCOLOR); + // axesBatch.stage(colorTarget, ZCOLOR); + // axesBatch.stage(vertexTarget, 0, 0, LEN); + vertices[i++] = new Vertex(new Vector3f(0f, 0f, LEN), ZCOLOR); + // arrow + // axesBatch.stage(colorTarget, ZCOLOR); + // axesBatch.stage(vertexTarget, -HEAD, 0, LEN - HEAD); + vertices[i++] = new Vertex(new Vector3f(-HEAD, 0f, LEN - HEAD), ZCOLOR); + // axesBatch.stage(colorTarget, ZCOLOR); + // axesBatch.stage(vertexTarget, 0, 0, LEN); + vertices[i++] = new Vertex(new Vector3f(0f, 0f, LEN), ZCOLOR); + // axesBatch.stage(colorTarget, ZCOLOR); + // axesBatch.stage(vertexTarget, 0, 0, LEN); + vertices[i++] = new Vertex(new Vector3f(0f, 0f, LEN), ZCOLOR); + // axesBatch.stage(colorTarget, ZCOLOR); + // axesBatch.stage(vertexTarget, HEAD, 0, LEN - HEAD); + vertices[i++] = new Vertex(new Vector3f(HEAD, 0f, LEN - HEAD), ZCOLOR); + // Z + // axesBatch.stage(colorTarget, ZCOLOR); + // axesBatch.stage(vertexTarget, -HEAD, HEAD, LEN + HEAD); + vertices[i++] = new Vertex(new Vector3f(-HEAD, HEAD, LEN + HEAD), ZCOLOR); + // axesBatch.stage(colorTarget, ZCOLOR); + // axesBatch.stage(vertexTarget, HEAD, HEAD, LEN + HEAD); + vertices[i++] = new Vertex(new Vector3f(HEAD, HEAD, LEN + HEAD), ZCOLOR); + // axesBatch.stage(colorTarget, ZCOLOR); + // axesBatch.stage(vertexTarget, HEAD, HEAD, LEN + HEAD); + vertices[i++] = new Vertex(new Vector3f(HEAD, HEAD, LEN + HEAD), ZCOLOR); + // axesBatch.stage(colorTarget, ZCOLOR); + // axesBatch.stage(vertexTarget, -HEAD, -HEAD, LEN + HEAD); + vertices[i++] = new Vertex(new Vector3f(-HEAD, -HEAD, LEN + HEAD), ZCOLOR); + // axesBatch.stage(colorTarget, ZCOLOR); + // axesBatch.stage(vertexTarget, -HEAD, -HEAD, LEN + HEAD); + vertices[i++] = new Vertex(new Vector3f(-HEAD, -HEAD, LEN + HEAD), ZCOLOR); + // axesBatch.stage(colorTarget, ZCOLOR); + // axesBatch.stage(vertexTarget, HEAD, -HEAD, LEN + HEAD); + vertices[i++] = new Vertex(new Vector3f(HEAD, -HEAD, LEN + HEAD), ZCOLOR); + + + // Staging buffer so our VB can be device local (most performant memory) + CVKBuffer cvkStagingBuffer = CVKBuffer.Create(size, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKAxesRenderable.CreateVertexBuffer cvkStagingBuffer"); + + PointerBuffer data = stack.mallocPointer(1); + vkMapMemory(CVKDevice.GetVkDevice(), cvkStagingBuffer.GetMemoryBufferHandle(), 0, size, 0, data); + if (VkFailed(ret)) { return ret; } + { + Vertex.CopyTo(data.getByteBuffer(0, size), vertices); + } + vkUnmapMemory(CVKDevice.GetVkDevice(), cvkStagingBuffer.GetMemoryBufferHandle()); + + // Create and stage into the actual VB which will be device local + cvkVertexBuffer = CVKBuffer.Create(size, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + "CVKAxesRenderable.CreateVertexBuffers cvkStagingBuffer"); + cvkVertexBuffer.CopyFrom(cvkStagingBuffer); + + // Cleanup + cvkStagingBuffer.Destroy(); + + SetVertexBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public int GetVertexCount() { return NUMBER_OF_VERTICES; } + + private void DestroyVertexBuffer() { + if (null != cvkVertexBuffer) { + cvkVertexBuffer.Destroy(); + cvkVertexBuffer = null; + + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + } + } + + + // ========================> Push constants <======================== \\ + + private int CreatePushConstants() { + // Initialise push constants to identity mtx + pushConstants = memAlloc(Matrix44f.BYTES); + PutMatrix44f(pushConstants, IDENTITY_44F); + pushConstants.flip(); + + return VK_SUCCESS; + } + + private void UpdatePushConstants(){ + CVKAssertNotNull(cvkSwapChain); + + final int[] viewport = cvkSwapChain.GetViewport(); + final int dx = cvkSwapChain.GetWidth() / 2 - AXES_OFFSET; + final int dy = cvkSwapChain.GetHeight() / 2 - AXES_OFFSET; + pScale = CalculateProjectionScale(viewport); + Graphics3DUtilities.moveByProjection(ZERO_3F, IDENTITY_44F, viewport, dx, dy, topRightCorner); + + // LIFTED FROM AxesRenerable.display(...) + // Extract the rotation matrix from the mvp matrix. + final Matrix44f rotationMatrix = new Matrix44f(); + cvkVisualProcessor.getDisplayModelViewProjectionMatrix().getRotationMatrix(rotationMatrix); + + // Scale down to size. + final Matrix44f scalingMatrix = new Matrix44f(); + scalingMatrix.makeScalingMatrix(pScale, pScale, 0); + final Matrix44f srMatrix = new Matrix44f(); + srMatrix.multiply(scalingMatrix, rotationMatrix); + + // Translate to the top right corner. + final Matrix44f translationMatrix = new Matrix44f(); + translationMatrix.makeTranslationMatrix(topRightCorner.getX(), + topRightCorner.getY(), + topRightCorner.getZ()); + + // Calculate the model-view-projection matrix + final Matrix44f mvpMatrix = new Matrix44f(); + mvpMatrix.multiply(translationMatrix, srMatrix); + + // Update the push constants data + PutMatrix44f(pushConstants, mvpMatrix); + pushConstants.flip(); + } + + private void DestroyPushConstants() { + if (pushConstants != null) { + memFree(pushConstants); + pushConstants = null; + } + } + + + // ========================> Command buffers <======================== \\ + + public int CreateCommandBuffer() { + CVKAssertNotNull(cvkSwapChain); + + cvkDisplayCommandBuffer = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_SECONDARY, + GetLogger(), + "CVKAxesRenderable cvkDisplayCommandBuffer"); + + SetCommandBuffersState(CVK_RESOURCE_CLEAN); + + return VK_SUCCESS; + } + + @Override + public VkCommandBuffer GetDisplayCommandBuffer(int imageIndex) { + return cvkDisplayCommandBuffer.GetVKCommandBuffer(); + } + + @Override + public int RecordDisplayCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int imageIndex) { + CVKAssertNotNull(cvkSwapChain); + cvkVisualProcessor.VerifyInRenderThread(); + int ret; + + CVKAssertNotNull(cvkDisplayCommandBuffer); + CVKAssertNotNull(displayPipelines.get(imageIndex)); + + ret = cvkDisplayCommandBuffer.BeginRecordSecondary(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, + inheritanceInfo); + if (VkFailed(ret)) { return ret; } + + cvkDisplayCommandBuffer.SetViewPort(cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight()); + cvkDisplayCommandBuffer.SetScissor(cvkVisualProcessor.GetCanvas().GetCurrentSurfaceExtent()); + cvkDisplayCommandBuffer.BindGraphicsPipeline(displayPipelines.get(imageIndex)); + cvkDisplayCommandBuffer.BindVertexInput(cvkVertexBuffer.GetBufferHandle()); + cvkDisplayCommandBuffer.PushConstants(hPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, pushConstants); + + cvkDisplayCommandBuffer.Draw(GetVertexCount()); + + ret = cvkDisplayCommandBuffer.FinishRecord(); + if (VkFailed(ret)) { return ret; } + + return ret; + } + + private void DestroyCommandBuffer() { + if (null != cvkDisplayCommandBuffer) { + cvkDisplayCommandBuffer.Destroy(); + cvkDisplayCommandBuffer = null; + + SetCommandBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + } + } + + + // ========================> Descriptors <======================== \\ + + @Override + public void IncrementDescriptorTypeRequirements(CVKDescriptorPoolRequirements reqs, CVKDescriptorPoolRequirements perImageReqs) { + // No descriptor sets required because axes use push constants instead of descriptor bound uniform buffers. + } + + @Override + public int DestroyDescriptorPoolResources() { + return VK_SUCCESS; + } + + + // ========================> Pipelines <======================== \\ + + private int CreatePipelineLayout() { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + + int ret; + try (MemoryStack stack = stackPush()) { + VkPushConstantRange.Buffer pushConstantRange; + pushConstantRange = VkPushConstantRange.callocStack(1, stack); + pushConstantRange.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + pushConstantRange.size(Matrix44f.BYTES); + pushConstantRange.offset(0); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = VkPipelineLayoutCreateInfo.callocStack(stack); + pipelineLayoutInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO); + pipelineLayoutInfo.pPushConstantRanges(pushConstantRange); + LongBuffer pPipelineLayout = stack.longs(VK_NULL_HANDLE); + ret = vkCreatePipelineLayout(CVKDevice.GetVkDevice(), pipelineLayoutInfo, null, pPipelineLayout); + if (VkFailed(ret)) { return ret; } + hPipelineLayout = pPipelineLayout.get(0); + CVKAssertNotNull(hPipelineLayout); + } + return ret; + } + + private void DestroyPipelineLayout() { + if (hPipelineLayout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(CVKDevice.GetVkDevice(), hPipelineLayout, null); + hPipelineLayout = VK_NULL_HANDLE; + } + } + + + // ========================> Display <======================== \\ + + @Override + public boolean NeedsDisplayUpdate() { + return vertexBuffersState != CVK_RESOURCE_CLEAN || + commandBuffersState != CVK_RESOURCE_CLEAN || + pipelinesState != CVK_RESOURCE_CLEAN; + } + + @Override + public int DisplayUpdate() { + cvkVisualProcessor.VerifyInRenderThread(); + + int ret = VK_SUCCESS; + + try (MemoryStack stack = stackPush()) { + if (vertexBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateVertexBuffer(stack); + if (VkFailed(ret)) { return ret; } + } + } + + if (commandBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateCommandBuffer(); + if (VkFailed(ret)) { return ret; } + } + + if (pipelinesState == CVK_RESOURCE_NEEDS_REBUILD) { + displayPipelines = new ArrayList<>(cvkSwapChain.GetImageCount()); + ret = CreatePipelines(cvkSwapChain.GetRenderPassHandle(), displayPipelines); + if (VkFailed(ret)) { return ret; } + } + + UpdatePushConstants(); + + return ret; + } + + + // ========================> Tasks <======================== \\ + + public CVKRenderUpdateTask TaskUpdateCamera() { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + UpdatePushConstants(); + }; + } + + + // ========================> Helpers <======================== \\ + + private float CalculateProjectionScale(final int[] viewport) { + // calculate the number of pixels a scene object of y-length 1 projects to. + final Vector3f unitPosition = new Vector3f(0, 1, 0); + final Vector4f projectedOrigin = new Vector4f(); + Graphics3DUtilities.project(ZERO_3F, IDENTITY_44F, viewport, projectedOrigin); + final Vector4f projectedIdentity = new Vector4f(); + Graphics3DUtilities.project(unitPosition, IDENTITY_44F, viewport, projectedIdentity); + float yScale = projectedIdentity.a[1] - projectedOrigin.a[1]; + + // Vulkan flips the Y compared to GL, this has no effect in world space but when we are premultiplying + // by the projection matrix we enter clip space, and here we do need to flip Y. + yScale = -yScale; + + return 25.0f / yScale; + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKBlazesRenderable.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKBlazesRenderable.java new file mode 100644 index 0000000000..70a01814cb --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKBlazesRenderable.java @@ -0,0 +1,1358 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.renderables; + +import au.gov.asd.tac.constellation.utilities.color.ConstellationColor; +import au.gov.asd.tac.constellation.utilities.graphics.Matrix44f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector4f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector4i; +import au.gov.asd.tac.constellation.utilities.visual.VisualAccess; +import au.gov.asd.tac.constellation.utilities.visual.VisualChange; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool.CVKDescriptorPoolRequirements; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import au.gov.asd.tac.constellation.visual.vulkan.CVKRenderUpdateTask; +import au.gov.asd.tac.constellation.visual.vulkan.CVKSwapChain; +import au.gov.asd.tac.constellation.visual.vulkan.CVKVisualProcessor; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_CLEAN; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_NEEDS_REBUILD; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_NEEDS_UPDATE; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKBuffer; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKCommandBuffer; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssert; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssertNotNull; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssertNull; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.IDENTITY_44F; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.PutMatrix44f; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkFailed; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkSucceeded; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.checkVKret; +import java.nio.ByteBuffer; +import java.nio.LongBuffer; +import java.util.ArrayList; +import java.util.List; +import org.lwjgl.system.MemoryStack; +import static org.lwjgl.system.MemoryStack.stackPush; +import org.lwjgl.system.MemoryUtil; +import static org.lwjgl.system.MemoryUtil.memFree; +import static org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_TRANSFER_DST_BIT; +import static org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_TRANSFER_SRC_BIT; +import static org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; +import static org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_LEVEL_SECONDARY; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT; +import static org.lwjgl.vulkan.VK10.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +import static org.lwjgl.vulkan.VK10.VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32B32A32_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32B32A32_SINT; +import static org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; +import static org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; +import static org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.VK_SHADER_STAGE_FRAGMENT_BIT; +import static org.lwjgl.vulkan.VK10.VK_SHADER_STAGE_GEOMETRY_BIT; +import static org.lwjgl.vulkan.VK10.VK_SHADER_STAGE_VERTEX_BIT; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; +import static org.lwjgl.vulkan.VK10.VK_SUCCESS; +import static org.lwjgl.vulkan.VK10.VK_VERTEX_INPUT_RATE_VERTEX; +import static org.lwjgl.vulkan.VK10.vkAllocateDescriptorSets; +import static org.lwjgl.vulkan.VK10.vkCreateDescriptorSetLayout; +import static org.lwjgl.vulkan.VK10.vkCreatePipelineLayout; +import static org.lwjgl.vulkan.VK10.vkDestroyDescriptorSetLayout; +import static org.lwjgl.vulkan.VK10.vkDestroyPipelineLayout; +import static org.lwjgl.vulkan.VK10.vkFreeDescriptorSets; +import static org.lwjgl.vulkan.VK10.vkUpdateDescriptorSets; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkCommandBufferInheritanceInfo; +import org.lwjgl.vulkan.VkDescriptorBufferInfo; +import org.lwjgl.vulkan.VkDescriptorSetAllocateInfo; +import org.lwjgl.vulkan.VkDescriptorSetLayoutBinding; +import org.lwjgl.vulkan.VkDescriptorSetLayoutCreateInfo; +import org.lwjgl.vulkan.VkPipelineLayoutCreateInfo; +import org.lwjgl.vulkan.VkPushConstantRange; +import org.lwjgl.vulkan.VkVertexInputAttributeDescription; +import org.lwjgl.vulkan.VkVertexInputBindingDescription; +import org.lwjgl.vulkan.VkWriteDescriptorSet; + + +/******************************************************************************* + * CVKBlazesRenderable + * + * + *******************************************************************************/ + +public class CVKBlazesRenderable extends CVKRenderable { + // Resources recreated with the swap chain (dependent on the image count) + private LongBuffer pDescriptorSets = null; + private CVKCommandBuffer cvkDisplayCommandBuffer; + private CVKBuffer cvkVertexBuffer; + private CVKBuffer cvkVertexUniformBuffer; + private CVKBuffer cvkGeometryUniformBuffer; + private CVKBuffer cvkFragmentUniformBuffer; + + // The UBO staging buffers are a known size so created outside user events + private CVKBuffer cvkVertexUBStagingBuffer = null; + private CVKBuffer cvkGeometryUBStagingBuffer = null; + private CVKBuffer cvkFragmentUBStagingBuffer = null; + private final VertexUniformBufferObject vertexUBO = new VertexUniformBufferObject(); + private final GeometryUniformBufferObject geometryUBO = new GeometryUniformBufferObject(); + private final FragmentUniformBufferObject fragmentUBO = new FragmentUniformBufferObject(); + + // Resources recreated only through user events + private int vertexCount = 0; + private CVKBuffer cvkVertexStagingBuffer = null; + + // Resources we don't own but use and must track so we know when to update + // our descriptors + private long hPositionBuffer = VK_NULL_HANDLE; + private long hPositionBufferView = VK_NULL_HANDLE; + + // Push constants for shaders contains the MV matrix and drawHitTest int + private ByteBuffer modelViewPushConstants = null; + private ByteBuffer hitTestPushConstants = null; + private static final int MODEL_VIEW_PUSH_CONSTANT_STAGES = VK_SHADER_STAGE_VERTEX_BIT; + private static final int HIT_TEST_PUSH_CONSTANT_STAGES = VK_SHADER_STAGE_FRAGMENT_BIT; + private static final int MODEL_VIEW_PUSH_CONSTANT_SIZE = Matrix44f.BYTES; + private static final int HIT_TEST_PUSH_CONSTANT_SIZE = Integer.BYTES; + + + // ========================> Classes <======================== \\ + + protected static class Vertex { + // This looks a little weird for Java, but LWJGL and JOGL both require + // contiguous memory which is passed to the native GL or VK libraries. + private static final int BYTES = Vector4f.BYTES + Vector4i.BYTES; + private static final int OFFSETOF_DATA = Vector4f.BYTES; + private static final int OFFSET_CLR = 0; + private static final int BINDING = 0; + private final Vector4f colour = new Vector4f(); + private final Vector4i data = new Vector4i(); + + private Vertex() {} + + + public Vertex(ConstellationColor inColour, + float visibility, + int vertexID, + int angle) { + colour.a[0] = inColour.getRed(); + colour.a[1] = inColour.getGreen(); + colour.a[2] = inColour.getBlue(); + colour.a[3] = visibility; + + data.a[0] = vertexID; + data.a[1] = -1; + data.a[2] = angle; + data.a[3] = 0; + } + + public void CopyToSequentially(ByteBuffer buffer) { + buffer.putFloat(colour.a[0]); + buffer.putFloat(colour.a[1]); + buffer.putFloat(colour.a[2]); + buffer.putFloat(colour.a[3]); + buffer.putInt(data.a[0]); + buffer.putInt(data.a[1]); + buffer.putInt(data.a[2]); + buffer.putInt(data.a[3]); + } + + /** + * A VkVertexInputBindingDescription defines the rate at which data is + * consumed by vertex shader (per vertex or per instance). + * The input rate determines whether to move to the next data entry after + * each vertex or after each instance. + * The binding description also defines the vertex stride, the number of + * bytes that must be stepped from vertex n-1 to vertex n. + * + * @return Binding description for the FPS vertex type + */ + protected static VkVertexInputBindingDescription.Buffer GetBindingDescription() { + + VkVertexInputBindingDescription.Buffer bindingDescription = + VkVertexInputBindingDescription.callocStack(1); + + // If we bind multiple vertex buffers with different descriptions + // this is the index of this description occupies in the array of + // bound descriptions. + bindingDescription.binding(BINDING); + bindingDescription.stride(Vertex.BYTES); + bindingDescription.inputRate(VK_VERTEX_INPUT_RATE_VERTEX); + + return bindingDescription; + } + + /** + * A VkVertexInputAttributeDescription describes each element int the + * vertex buffer. + * binding: matches the binding member of VkVertexInputBindingDescription + * location: corresponds to the layout(location = #) in the vertex shader + * for this element (0 for data, 1 for bkgClr). + * format: format the shader will interpret this as. + * offset: bytes from the start of the vertex this attribute starts at + * + * @return + */ + protected static VkVertexInputAttributeDescription.Buffer GetAttributeDescriptions() { + + VkVertexInputAttributeDescription.Buffer attributeDescriptions = + VkVertexInputAttributeDescription.callocStack(2); + + // vColor + VkVertexInputAttributeDescription posDescription = attributeDescriptions.get(0); + posDescription.binding(BINDING); + posDescription.location(0); + posDescription.format(VK_FORMAT_R32G32B32A32_SFLOAT); + posDescription.offset(OFFSET_CLR); + + // data + VkVertexInputAttributeDescription colorDescription = attributeDescriptions.get(1); + colorDescription.binding(BINDING); + colorDescription.location(1); + colorDescription.format(VK_FORMAT_R32G32B32A32_SINT); + colorDescription.offset(OFFSETOF_DATA); + + return attributeDescriptions.rewind(); + } + } + + @Override + protected VkVertexInputBindingDescription.Buffer GetVertexBindingDescription() { + return Vertex.GetBindingDescription(); + } + + @Override + protected VkVertexInputAttributeDescription.Buffer GetVertexAttributeDescriptions() { + return Vertex.GetAttributeDescriptions(); + } + + protected static class VertexUniformBufferObject { + public float morphMix = 0; + private static Integer padding = null; + + protected static int SizeOf() { + if (padding == null) { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + final int minAlignment = CVKDevice.GetMinUniformBufferAlignment(); + + // The matrices are 64 bytes each so should line up on a boundary (unless the minimum alignment is huge) + CVKAssert(minAlignment <= (16 * Float.BYTES)); + + int sizeof = 1 * Float.BYTES; // morphMix + final int overrun = sizeof % minAlignment; + padding = overrun > 0 ? minAlignment - overrun : 0; + } + + return 1 * Float.BYTES + // morphMix + padding; + } + + private void CopyTo(ByteBuffer buffer) { + buffer.putFloat(morphMix); + + for (int i = 0; i < padding; ++i) { + buffer.put((byte)0); + } + } + } + + protected static class GeometryUniformBufferObject { + private final Matrix44f pMatrix = new Matrix44f(); + private float scale; + private float visibilityLow; + private float visibilityHigh; + private static Integer padding = null; + + protected static int SizeOf() { + if (padding == null) { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + final int minAlignment = CVKDevice.GetMinUniformBufferAlignment(); + + // The matrices are 64 bytes each so should line up on a boundary (unless the minimum alignment is huge) + CVKAssert(minAlignment <= (Matrix44f.BYTES)); + + int sizeof = Matrix44f.BYTES + // pMatrix + 1 * Float.BYTES + // scale + 1 * Float.BYTES + // visibilityLow + 1 * Float.BYTES; // visibilityHigh + + final int overrun = sizeof % minAlignment; + padding = overrun > 0 ? minAlignment - overrun : 0; + } + + return Matrix44f.BYTES + // pMatrix + 1 * Float.BYTES + // scale + 1 * Float.BYTES + // visibilityLow + 1 * Float.BYTES + // visibilityHigh + padding; + } + + private void CopyTo(ByteBuffer buffer) { + PutMatrix44f(buffer, pMatrix); + + buffer.putFloat(scale); + buffer.putFloat(visibilityLow); + buffer.putFloat(visibilityHigh); + + for (int i = 0; i < padding; ++i) { + buffer.put((byte)0); + } + } + } + + protected static class FragmentUniformBufferObject { + private float opacity; + private static Integer padding = null; + + protected static int SizeOf() { + if (padding == null) { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + final int minAlignment = CVKDevice.GetMinUniformBufferAlignment(); + + // The matrices are 64 bytes each so should line up on a boundary (unless the minimum alignment is huge) + CVKAssert(minAlignment <= (Matrix44f.BYTES)); + + int sizeof = 1 * Float.BYTES; // opacity + + final int overrun = sizeof % minAlignment; + padding = overrun > 0 ? minAlignment - overrun : 0; + } + + return 1 * Float.BYTES + // opacity + padding; + } + + private void CopyTo(ByteBuffer buffer) { + buffer.putFloat(opacity); + + for (int i = 0; i < padding; ++i) { + buffer.put((byte)0); + } + } + } + + + // ========================> Shaders <======================== \\ + + @Override + protected String GetVertexShaderName() { return "Blaze.vs"; } + + @Override + protected String GetGeometryShaderName() { return "Blaze.gs"; } + + @Override + protected String GetFragmentShaderName() { return "Blaze.fs"; } + + + // ========================> Lifetime <======================== \\ + + public CVKBlazesRenderable(CVKVisualProcessor visualProcessor) { + super(visualProcessor); + depthTest = false; + } + + private void CreateUBOStagingBuffers() { + cvkVertexUBStagingBuffer = CVKBuffer.Create(VertexUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKBlazesRenderable.CreateUBOStagingBuffers cvkVertexUBStagingBuffer"); + cvkGeometryUBStagingBuffer = CVKBuffer.Create(GeometryUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKBlazesRenderable.CreateUBOStagingBuffers cvkGeometryUBStagingBuffer"); + cvkFragmentUBStagingBuffer = CVKBuffer.Create(FragmentUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKBlazesRenderable.CreateUBOStagingBuffers cvkFragmentUBStagingBuffer"); + } + + @Override + public int Initialise() { + int ret = super.Initialise(); + if (VkFailed(ret)) { return ret; } + + // Check for double initialisation + CVKAssert(hDescriptorLayout == VK_NULL_HANDLE); + + CreatePushConstants(); + + ret = CreateDescriptorLayout(); + if (VkFailed(ret)) { return ret; } + + ret = CreatePipelineLayout(); + if (VkFailed(ret)) { return ret; } + + CreateUBOStagingBuffers(); + + return ret; + } + + private void DestroyStagingBuffers() { + if (cvkVertexStagingBuffer != null) { + cvkVertexStagingBuffer.Destroy(); + cvkVertexStagingBuffer = null; + } + if (cvkVertexUBStagingBuffer != null) { + cvkVertexUBStagingBuffer.Destroy(); + cvkVertexUBStagingBuffer = null; + } + if (cvkGeometryUBStagingBuffer != null) { + cvkGeometryUBStagingBuffer.Destroy(); + cvkGeometryUBStagingBuffer = null; + } + if (cvkFragmentUBStagingBuffer != null) { + cvkFragmentUBStagingBuffer.Destroy(); + cvkFragmentUBStagingBuffer = null; + } + } + + @Override + public void Destroy() { + DestroyVertexBuffer(); + DestroyVertexUniformBuffer(); + DestroyGeometryUniformBuffer(); + DestroyFragmentUniformBuffer(); + DestroyDescriptorSets(); + DestroyDescriptorLayout(); + DestroyPipelines(); + DestroyPipelineLayout(); + DestroyCommandBuffer(); + DestroyStagingBuffers(); + DestroyPushConstants(); + + CVKAssertNull(cvkVertexBuffer); + CVKAssertNull(cvkVertexUniformBuffer); + CVKAssertNull(cvkGeometryUniformBuffer); + CVKAssertNull(cvkFragmentUniformBuffer); + CVKAssertNull(pDescriptorSets); + CVKAssertNull(hDescriptorLayout); + CVKAssertNull(cvkDisplayCommandBuffer); + CVKAssertNull(displayPipelines); + CVKAssertNull(hPipelineLayout); + CVKAssertNull(modelViewPushConstants); + CVKAssertNull(hitTestPushConstants); + } + + + // ========================> Swap chain <======================== \\ + + @Override + protected int DestroySwapChainResources() { + this.cvkSwapChain = null; + + // We only need to recreate these resources if the number of images in + // the swapchain changes or if this is the first call after the initial + // swapchain is created. + if (displayPipelines != null && swapChainImageCountChanged) { + DestroyVertexBuffer(); + DestroyVertexUniformBuffer(); + DestroyGeometryUniformBuffer(); + DestroyFragmentUniformBuffer(); + DestroyDescriptorSets(); + DestroyCommandBuffer(); + DestroyPipelines(); + DestroyCommandBuffer(); + } + + return VK_SUCCESS; + } + + @Override + public int SetNewSwapChain(CVKSwapChain newSwapChain) { + int ret = super.SetNewSwapChain(newSwapChain); + if (VkFailed(ret)) { return ret; } + + if (swapChainImageCountChanged) { + // The number of images has changed, we need to rebuild all image + // buffered resources + SetVertexUniformBufferState(CVK_RESOURCE_NEEDS_REBUILD); + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_REBUILD); + SetFragmentUniformBufferState(CVK_RESOURCE_NEEDS_REBUILD); + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + SetCommandBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + SetDescriptorSetsState(CVK_RESOURCE_NEEDS_REBUILD); + SetPipelinesState(CVK_RESOURCE_NEEDS_REBUILD); + } else { + // View frustum and projection matrix likely have changed. We don't + // need to rebuild our displayPipelines as the frustum is set by dynamic + // state in RecordDisplayCommandBuffer + if (geometryUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + } + + return ret; + } + + + // ========================> Vertex buffers <======================== \\ + + private int CreateVertexBuffer() { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNull(cvkVertexBuffer); + + int ret = VK_SUCCESS; + + // We can only create vertex buffers if we have something to put in them + if (cvkVertexStagingBuffer.GetBufferSize() > 0) { + cvkVertexBuffer = CVKBuffer.Create(cvkVertexStagingBuffer.GetBufferSize(), + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + "CVKBlazesRenderable cvkVertexBuffer"); + + // Populate them with some values + return UpdateVertexBuffer(); + } + + return ret; + } + + private int UpdateVertexBuffer() { + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssert(cvkVertexStagingBuffer != null); + CVKAssert(cvkVertexBuffer != null); + CVKAssert(cvkVertexStagingBuffer.GetBufferSize() == cvkVertexBuffer.GetBufferSize()); + int ret = VK_SUCCESS; + + ret = cvkVertexBuffer.CopyFrom(cvkVertexStagingBuffer); + if (VkFailed(ret)) { return ret; } + + // Note the staging buffer is not freed as we can simplify the update tasks + // by just updating it and then copying it over again during ProcessRenderTasks(). + SetVertexBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public int GetVertexCount() { return cvkVisualProcessor.GetDrawFlags().drawConnections() ? vertexCount : 0; } + + private void DestroyVertexBuffer() { + if (cvkVertexBuffer != null) { + cvkVertexBuffer.Destroy(); + cvkVertexBuffer = null; + } + } + + + // ========================> Uniform buffers <======================== \\ + + private int CreateVertexUniformBuffers() { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNull(cvkVertexUniformBuffer); + + cvkVertexUniformBuffer = CVKBuffer.Create(VertexUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + "CVKBlazesRenderable cvkVertexUniformBuffer"); + return UpdateVertexUniformBuffer(); + } + + private int UpdateVertexUniformBuffer() { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkVertexUBStagingBuffer); + CVKAssertNotNull(cvkVertexUniformBuffer); + + int ret = VK_SUCCESS; + + // Populate the UBO. This is easy to deal with, but not super efficient + // as we are effectively staging into the staging buffer below. + vertexUBO.morphMix = cvkVisualProcessor.getDisplayCamera().getMix(); + + // Staging buffer so our VBO can be device local (most performant memory) + ByteBuffer pMemory = cvkVertexUBStagingBuffer.StartMemoryMap(0, VertexUniformBufferObject.SizeOf()); + { + vertexUBO.CopyTo(pMemory); + } + cvkVertexUBStagingBuffer.EndMemoryMap(); + pMemory = null; + + // Copy the staging buffer into the uniform buffer on the device + ret = cvkVertexUniformBuffer.CopyFrom(cvkVertexUBStagingBuffer); + if (VkFailed(ret)) { return ret; } + + UpdateVertexPushConstants(); + + // We are done, reset the resource state + SetVertexUniformBufferState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private void DestroyVertexUniformBuffer() { + if (cvkVertexUniformBuffer != null) { + cvkVertexUniformBuffer.Destroy(); + cvkVertexUniformBuffer = null; + } + } + + private int CreateGeometryUniformBuffer() { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNull(cvkGeometryUniformBuffer); + + cvkGeometryUniformBuffer = CVKBuffer.Create(GeometryUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + "CVKBlazesRenderable cvkGeometryUniformBuffer"); + return UpdateGeometryUniformBuffer(); + } + + private int UpdateGeometryUniformBuffer() { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkGeometryUBStagingBuffer); + CVKAssertNotNull(cvkGeometryUniformBuffer); + + int ret = VK_SUCCESS; + + // Populate the UBO. This is easy to deal with, but not super efficient + // as we are effectively staging into the staging buffer below. + geometryUBO.pMatrix.set(cvkVisualProcessor.GetProjectionMatrix()); + geometryUBO.visibilityLow = cvkVisualProcessor.getDisplayCamera().getVisibilityLow(); + geometryUBO.visibilityHigh = cvkVisualProcessor.getDisplayCamera().getVisibilityHigh(); + + // Staging buffer so our VBO can be device local (most performant memory) + ByteBuffer pMemory = cvkGeometryUBStagingBuffer.StartMemoryMap(0, GeometryUniformBufferObject.SizeOf()); + { + geometryUBO.CopyTo(pMemory); + } + cvkGeometryUBStagingBuffer.EndMemoryMap(); + pMemory = null; + + // Copy the staging buffer into the uniform buffer on the device + cvkGeometryUniformBuffer.CopyFrom(cvkGeometryUBStagingBuffer); + if (VkFailed(ret)) { return ret; } + + // We are done, reset the resource state + SetGeometryUniformBufferState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private void DestroyGeometryUniformBuffer() { + if (cvkGeometryUniformBuffer != null) { + cvkGeometryUniformBuffer.Destroy(); + cvkGeometryUniformBuffer = null; + } + } + + private int CreateFragmentUniformBuffer() { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNull(cvkFragmentUniformBuffer); + + cvkFragmentUniformBuffer = CVKBuffer.Create(FragmentUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + "CVKBlazesRenderable cvkFragmentUniformBuffer"); + return UpdateFragmentUniformBuffer(); + } + + private int UpdateFragmentUniformBuffer() { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkFragmentUBStagingBuffer); + CVKAssertNotNull(cvkFragmentUniformBuffer); + + int ret = VK_SUCCESS; + + // Staging buffer so our VBO can be device local (most performant memory) + ByteBuffer pMemory = cvkFragmentUBStagingBuffer.StartMemoryMap(0, FragmentUniformBufferObject.SizeOf()); + { + fragmentUBO.CopyTo(pMemory); + } + cvkFragmentUBStagingBuffer.EndMemoryMap(); + pMemory = null; + + // Copy the staging buffer into the uniform buffer on the device + cvkFragmentUniformBuffer.CopyFrom(cvkFragmentUBStagingBuffer); + if (VkFailed(ret)) { return ret; } + + // We are done, reset the resource state + SetFragmentUniformBufferState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private void DestroyFragmentUniformBuffer() { + if (cvkFragmentUniformBuffer != null) { + cvkFragmentUniformBuffer.Destroy(); + cvkFragmentUniformBuffer = null; + } + } + + // ========================> Push constants <======================== \\ + + private int CreatePushConstants() { + // Initialise push constants to identity mtx + modelViewPushConstants = MemoryUtil.memAlloc(MODEL_VIEW_PUSH_CONSTANT_SIZE); + PutMatrix44f(modelViewPushConstants, IDENTITY_44F); + + // Set DrawHitTest to false + hitTestPushConstants = MemoryUtil.memAlloc(HIT_TEST_PUSH_CONSTANT_SIZE); + hitTestPushConstants.putInt(0); + + modelViewPushConstants.flip(); + hitTestPushConstants.flip(); + + return VK_SUCCESS; + } + + private void UpdateVertexPushConstants(){ + CVKAssertNotNull(cvkSwapChain); + + modelViewPushConstants.clear(); + PutMatrix44f(modelViewPushConstants, cvkVisualProcessor.getDisplayModelViewMatrix()); + modelViewPushConstants.flip(); + } + + protected void UpdatePushConstantsHitTest(boolean drawHitTest){ + CVKAssertNotNull(cvkSwapChain); + + hitTestPushConstants.clear(); + + if (drawHitTest) { + hitTestPushConstants.putInt(1); + } else { + hitTestPushConstants.putInt(0); + } + + hitTestPushConstants.flip(); + } + + private void DestroyPushConstants() { + if (modelViewPushConstants != null) { + memFree(modelViewPushConstants); + modelViewPushConstants = null; + } + + if (hitTestPushConstants != null) { + memFree(hitTestPushConstants); + hitTestPushConstants = null; + } + } + + + // ========================> Command buffers <======================== \\ + + public int CreateCommandBuffers(){ + CVKAssertNotNull(cvkSwapChain); + + cvkDisplayCommandBuffer = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_SECONDARY, GetLogger(), "CVKLoopsRenderable cvkDisplayCommandBuffer"); + SetCommandBuffersState(CVK_RESOURCE_CLEAN); + + return VK_SUCCESS; + } + + @Override + public VkCommandBuffer GetDisplayCommandBuffer(int imageIndex) { + return cvkDisplayCommandBuffer.GetVKCommandBuffer(); + } + + @Override + public int RecordDisplayCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int imageIndex){ + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(CVKDevice.GetCommandPoolHandle()); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + CVKAssertNotNull(cvkDisplayCommandBuffer); + CVKAssert(displayPipelines.get(imageIndex) != null); + + cvkDisplayCommandBuffer.BeginRecordSecondary(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, inheritanceInfo); + + cvkDisplayCommandBuffer.SetViewPort(cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight()); + cvkDisplayCommandBuffer.SetScissor(cvkVisualProcessor.GetCanvas().GetCurrentSurfaceExtent()); + + cvkDisplayCommandBuffer.BindGraphicsPipeline(displayPipelines.get(imageIndex)); + cvkDisplayCommandBuffer.BindVertexInput(cvkVertexBuffer.GetBufferHandle()); + + // Push MV matrix to the vertex shader + cvkDisplayCommandBuffer.PushConstants(hPipelineLayout, MODEL_VIEW_PUSH_CONSTANT_STAGES, 0, modelViewPushConstants); + + // Push drawHitTest flag to the geometry shader + cvkDisplayCommandBuffer.PushConstants(hPipelineLayout, HIT_TEST_PUSH_CONSTANT_STAGES, Matrix44f.BYTES, hitTestPushConstants); + + cvkDisplayCommandBuffer.BindGraphicsDescriptorSets(hPipelineLayout, pDescriptorSets.get(imageIndex)); + + cvkDisplayCommandBuffer.Draw(GetVertexCount()); + + ret = cvkDisplayCommandBuffer.FinishRecord(); + if (VkFailed(ret)) { return ret; } + + return ret; + } + + private void DestroyCommandBuffer() { + if (null != cvkDisplayCommandBuffer) { + cvkDisplayCommandBuffer.Destroy(); + cvkDisplayCommandBuffer = null; + } + } + + + // ========================> Descriptors <======================== \\ + + private int CreateDescriptorLayout() { + int ret; + + try (MemoryStack stack = stackPush()) { + VkDescriptorSetLayoutBinding.Buffer bindings = VkDescriptorSetLayoutBinding.callocStack(4, stack); + + // 0: Vertex uniform buffer + VkDescriptorSetLayoutBinding vertexUBDSLB = bindings.get(0); + vertexUBDSLB.binding(0); + vertexUBDSLB.descriptorCount(1); + vertexUBDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + vertexUBDSLB.pImmutableSamplers(null); + vertexUBDSLB.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + + // 1: Vertex samplerBuffer (position buffer) + VkDescriptorSetLayoutBinding vertexSamplerDSLB = bindings.get(1); + vertexSamplerDSLB.binding(1); + vertexSamplerDSLB.descriptorCount(1); + vertexSamplerDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + vertexSamplerDSLB.pImmutableSamplers(null); + vertexSamplerDSLB.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + + // 2: Geometry uniform buffer + VkDescriptorSetLayoutBinding geometryUBDSLB = bindings.get(2); + geometryUBDSLB.binding(2); + geometryUBDSLB.descriptorCount(1); + geometryUBDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + geometryUBDSLB.pImmutableSamplers(null); + geometryUBDSLB.stageFlags(VK_SHADER_STAGE_GEOMETRY_BIT); + + // 3: Fragment uniform buffer + VkDescriptorSetLayoutBinding fragmentUBDSLB = bindings.get(3); + fragmentUBDSLB.binding(3); + fragmentUBDSLB.descriptorCount(1); + fragmentUBDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + fragmentUBDSLB.pImmutableSamplers(null); + fragmentUBDSLB.stageFlags(VK_SHADER_STAGE_FRAGMENT_BIT); + + VkDescriptorSetLayoutCreateInfo layoutInfo = VkDescriptorSetLayoutCreateInfo.callocStack(stack); + layoutInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO); + layoutInfo.pBindings(bindings); + + LongBuffer pDescriptorSetLayout = stack.mallocLong(1); + + ret = vkCreateDescriptorSetLayout(CVKDevice.GetVkDevice(), layoutInfo, null, pDescriptorSetLayout); + if (VkSucceeded(ret)) { + hDescriptorLayout = pDescriptorSetLayout.get(0); + GetLogger().info("CVKBlazesRenderable created hDescriptorLayout: 0x%016X", hDescriptorLayout); + } + } + return ret; + } + + private void DestroyDescriptorLayout() { + GetLogger().info("CVKBlazesRenderable destroying hDescriptorLayout: 0x%016X", hDescriptorLayout); + vkDestroyDescriptorSetLayout(CVKDevice.GetVkDevice(), hDescriptorLayout, null); + hDescriptorLayout = VK_NULL_HANDLE; + } + + private int CreateDescriptorSets(MemoryStack stack) { + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + // The same layout is used for each descriptor set. Each image has a + // an identical copy of the descriptor set so allow the GPU and CPU to + // desynchronise (when there are 2 or more images in the swapchain). + final int imageCount = cvkSwapChain.GetImageCount(); + LongBuffer layouts = stack.mallocLong(imageCount); + for (int i = 0; i < imageCount; ++i) { + layouts.put(i, hDescriptorLayout); + } + + VkDescriptorSetAllocateInfo allocInfo = VkDescriptorSetAllocateInfo.callocStack(stack); + allocInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO); + allocInfo.descriptorPool(cvkDescriptorPool.GetDescriptorPoolHandle()); + allocInfo.pSetLayouts(layouts); + + // Allocate the descriptor sets from the descriptor pool, they'll be unitialised + pDescriptorSets = MemoryUtil.memAllocLong(imageCount); + ret = vkAllocateDescriptorSets(CVKDevice.GetVkDevice(), allocInfo, pDescriptorSets); + if (VkFailed(ret)) { return ret; } + + for (int i = 0; i < pDescriptorSets.capacity(); ++i) { + GetLogger().info("CVKBlazesRenderable allocated hDescriptorSet %d: 0x%016X", i, pDescriptorSets.get(i)); + } + + return UpdateDescriptorSets(stack); + } + + // TODO: do we gain anything by having buffered UBOs? + private int UpdateDescriptorSets(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(pDescriptorSets); + CVKAssert(pDescriptorSets.capacity() > 0); + CVKAssertNotNull(cvkVertexUniformBuffer); + CVKAssertNotNull(cvkGeometryUniformBuffer); + CVKAssertNotNull(cvkFragmentUniformBuffer); + CVKAssertNotNull(cvkVertexUniformBuffer.GetBufferHandle()); + CVKAssertNotNull(cvkGeometryUniformBuffer.GetBufferHandle()); + CVKAssertNotNull(cvkFragmentUniformBuffer.GetBufferHandle()); + + int ret = VK_SUCCESS; + + final long positionBufferSize = cvkVisualProcessor.GetPositionBufferSize(); + hPositionBuffer = cvkVisualProcessor.GetPositionBufferHandle(); + hPositionBufferView = cvkVisualProcessor.GetPositionBufferViewHandle(); + CVKAssertNotNull(hPositionBuffer); + CVKAssertNotNull(hPositionBufferView); + + // - Descriptor info structs - + // We create these to describe the different resources we want to address + // in shaders. We have one info struct per resource. We then create a + // write descriptor set structure for each resource for each image. For + // buffered resources like the the uniform buffers we wait to set the + // buffer resource until the image loop below. + + // Struct for the uniform buffer used by Blaze.vs + VkDescriptorBufferInfo.Buffer vertexUniformBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + vertexUniformBufferInfo.buffer(cvkVertexUniformBuffer.GetBufferHandle()); + vertexUniformBufferInfo.offset(0); + vertexUniformBufferInfo.range(VertexUniformBufferObject.SizeOf()); + + // Struct for texel buffer (positions) used by Blaze.vs + VkDescriptorBufferInfo.Buffer positionsTexelBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + positionsTexelBufferInfo.buffer(hPositionBuffer); + positionsTexelBufferInfo.offset(0); + positionsTexelBufferInfo.range(positionBufferSize); + + // Struct for the uniform buffer used by Blaze.gs + VkDescriptorBufferInfo.Buffer geometryUniformBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + geometryUniformBufferInfo.buffer(cvkGeometryUniformBuffer.GetBufferHandle()); + geometryUniformBufferInfo.offset(0); + geometryUniformBufferInfo.range(GeometryUniformBufferObject.SizeOf()); + + // Struct for the uniform buffer used by Blaze.fs + VkDescriptorBufferInfo.Buffer fragmentUniformBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + fragmentUniformBufferInfo.buffer(cvkFragmentUniformBuffer.GetBufferHandle()); + fragmentUniformBufferInfo.offset(0); + fragmentUniformBufferInfo.range(FragmentUniformBufferObject.SizeOf()); + + // We need 4 write descriptors, 3 for uniform buffers and 1 for texel buffers + VkWriteDescriptorSet.Buffer descriptorWrites = VkWriteDescriptorSet.callocStack(4, stack); + + // Vertex uniform buffer + VkWriteDescriptorSet vertexUBDescriptorWrite = descriptorWrites.get(0); + vertexUBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + vertexUBDescriptorWrite.dstBinding(0); + vertexUBDescriptorWrite.dstArrayElement(0); + vertexUBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + vertexUBDescriptorWrite.descriptorCount(1); + vertexUBDescriptorWrite.pBufferInfo(vertexUniformBufferInfo); + + // Vertex texel buffer (positions) + VkWriteDescriptorSet positionsTBDescriptorWrite = descriptorWrites.get(1); + positionsTBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + positionsTBDescriptorWrite.dstBinding(1); + positionsTBDescriptorWrite.dstArrayElement(0); + positionsTBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + positionsTBDescriptorWrite.descriptorCount(1); + positionsTBDescriptorWrite.pBufferInfo(positionsTexelBufferInfo); + positionsTBDescriptorWrite.pTexelBufferView(stack.longs(hPositionBufferView)); + + // Geometry uniform buffer + VkWriteDescriptorSet geometryUBDescriptorWrite = descriptorWrites.get(2); + geometryUBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + geometryUBDescriptorWrite.dstBinding(2); + geometryUBDescriptorWrite.dstArrayElement(0); + geometryUBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + geometryUBDescriptorWrite.descriptorCount(1); + geometryUBDescriptorWrite.pBufferInfo(geometryUniformBufferInfo); + + // Fragment uniform buffer + VkWriteDescriptorSet fragmentUBDescriptorWrite = descriptorWrites.get(3); + fragmentUBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + fragmentUBDescriptorWrite.dstBinding(3); + fragmentUBDescriptorWrite.dstArrayElement(0); + fragmentUBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + fragmentUBDescriptorWrite.descriptorCount(1); + fragmentUBDescriptorWrite.pBufferInfo(fragmentUniformBufferInfo); + + final int imageCount = cvkSwapChain.GetImageCount(); + for (int i = 0; i < imageCount; ++i) { + // Set the descriptor set we're updating in each write struct + long descriptorSet = pDescriptorSets.get(i); + descriptorWrites.forEach(el -> {el.dstSet(descriptorSet);}); + + // Update the descriptors with a write and no copy + GetLogger().info("CVKBlazesRenderable updating descriptorSet: 0x%016X", descriptorSet); + vkUpdateDescriptorSets(CVKDevice.GetVkDevice(), descriptorWrites, null); + } + + SetDescriptorSetsState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private int DestroyDescriptorSets() { + int ret = VK_SUCCESS; + + if (pDescriptorSets != null) { + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(cvkDescriptorPool.GetDescriptorPoolHandle()); + GetLogger().fine("CVKBlazesRenderable returning %d descriptor sets to the pool", pDescriptorSets.capacity()); + + for (int i = 0; i < pDescriptorSets.capacity(); ++i) { + GetLogger().info("CVKBlazesRenderable freeing hDescriptorSet %d: 0x%016X", i, pDescriptorSets.get(i)); + } + + // After calling vkFreeDescriptorSets, all descriptor sets in pDescriptorSets are invalid. + ret = vkFreeDescriptorSets(CVKDevice.GetVkDevice(), cvkDescriptorPool.GetDescriptorPoolHandle(), pDescriptorSets); + pDescriptorSets = null; + checkVKret(ret); + } + + return ret; + } + + @Override + public int DestroyDescriptorPoolResources() { + int ret = VK_SUCCESS; + + if (cvkDescriptorPool != null) { + return DestroyDescriptorSets(); + } + + return ret; + } + + @Override + public void IncrementDescriptorTypeRequirements(CVKDescriptorPoolRequirements reqs, CVKDescriptorPoolRequirements perImageReqs) { + // Blaze.vs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]; + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER]; + + // Blaze.gs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]; + + // Blaze.fs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]; + + // One set per image + ++perImageReqs.poolDesciptorSetCount; + } + + @Override + public int SetNewDescriptorPool(CVKDescriptorPool newDescriptorPool) { + int ret = super.SetNewDescriptorPool(newDescriptorPool); + if (VkFailed(ret)) { return ret; } + SetDescriptorSetsState(CVK_RESOURCE_NEEDS_REBUILD); + return ret; + } + + + // ========================> Pipelines <======================== \\ + + private int CreatePipelineLayout() { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(hDescriptorLayout); + + int ret; + try (MemoryStack stack = stackPush()) { + VkPushConstantRange.Buffer pushConstantRange; + pushConstantRange = VkPushConstantRange.calloc(2); + pushConstantRange.get(0).stageFlags(MODEL_VIEW_PUSH_CONSTANT_STAGES); + pushConstantRange.get(0).size(MODEL_VIEW_PUSH_CONSTANT_SIZE); + pushConstantRange.get(0).offset(0); + + pushConstantRange.get(1).stageFlags(HIT_TEST_PUSH_CONSTANT_STAGES); + pushConstantRange.get(1).size(HIT_TEST_PUSH_CONSTANT_SIZE); + pushConstantRange.get(1).offset(MODEL_VIEW_PUSH_CONSTANT_SIZE); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = VkPipelineLayoutCreateInfo.callocStack(stack); + pipelineLayoutInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO); + pipelineLayoutInfo.pSetLayouts(stack.longs(hDescriptorLayout)); + pipelineLayoutInfo.pPushConstantRanges(pushConstantRange); + LongBuffer pPipelineLayout = stack.longs(VK_NULL_HANDLE); + ret = vkCreatePipelineLayout(CVKDevice.GetVkDevice(), pipelineLayoutInfo, null, pPipelineLayout); + if (VkFailed(ret)) { return ret; } + hPipelineLayout = pPipelineLayout.get(0); + CVKAssert(hPipelineLayout != VK_NULL_HANDLE); + } + return ret; + } + + private void DestroyPipelineLayout() { + if (hPipelineLayout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(CVKDevice.GetVkDevice(), hPipelineLayout, null); + hPipelineLayout = VK_NULL_HANDLE; + } + } + + + // ========================> Display <======================== \\ + + @Override + public boolean NeedsDisplayUpdate() { + // Race condition: icons owns the position buffer and the update task (on + // the visual processor thread) may not yet be complete. If this happens + // before the first update is finished icons will have a vertexCount of + // 0 so it won't be able to create the position buffer yet. If we can't + // get a handle to the current position buffer then we just skip updating + // until we can. + if (cvkVisualProcessor.GetPositionBufferHandle() == VK_NULL_HANDLE) { + return false; + } + + if (hPositionBuffer != cvkVisualProcessor.GetPositionBufferHandle() || + hPositionBufferView != cvkVisualProcessor.GetPositionBufferViewHandle()) { + if (descriptorSetsState != CVK_RESOURCE_NEEDS_REBUILD) { + descriptorSetsState = CVK_RESOURCE_NEEDS_UPDATE; + } + } + + return vertexCount > 0 && + (vertexUniformBufferState != CVK_RESOURCE_CLEAN || + geometryUniformBufferState != CVK_RESOURCE_CLEAN || + fragmentUniformBufferState != CVK_RESOURCE_CLEAN || + vertexBuffersState != CVK_RESOURCE_CLEAN || + commandBuffersState != CVK_RESOURCE_CLEAN || + descriptorSetsState != CVK_RESOURCE_CLEAN || + pipelinesState != CVK_RESOURCE_CLEAN); + } + + @Override + public int DisplayUpdate() { + int ret = VK_SUCCESS; + cvkVisualProcessor.VerifyInRenderThread(); + + try (MemoryStack stack = stackPush()) { + // Update vertex buffers + if (vertexBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + DestroyVertexBuffer(); + ret = CreateVertexBuffer(); + if (VkFailed(ret)) { return ret; } + } else if (vertexBuffersState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateVertexBuffer(); + if (VkFailed(ret)) { return ret; } + } + + // Vertex uniform buffer + if (vertexUniformBufferState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateVertexUniformBuffers(); + if (VkFailed(ret)) { return ret; } + } else if (vertexUniformBufferState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateVertexUniformBuffer(); + if (VkFailed(ret)) { return ret; } + } + + // Geometry uniform buffer + if (geometryUniformBufferState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateGeometryUniformBuffer(); + if (VkFailed(ret)) { return ret; } + } else if (geometryUniformBufferState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateGeometryUniformBuffer(); + if (VkFailed(ret)) { return ret; } + } + + // Fragment uniform buffer + if (fragmentUniformBufferState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateFragmentUniformBuffer(); + if (VkFailed(ret)) { return ret; } + } else if (fragmentUniformBufferState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateFragmentUniformBuffer(); + if (VkFailed(ret)) { return ret; } + } + + // Descriptors (binding values to shaders parameters) + if (descriptorSetsState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateDescriptorSets(stack); + if (VkFailed(ret)) { return ret; } + } else if (descriptorSetsState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateDescriptorSets(stack); + if (VkFailed(ret)) { return ret; } + } + + // Command buffers (rendering commands enqueued on the GPU) + if (commandBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateCommandBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Pipelines (all the render state and resources in one object) + if (pipelinesState == CVK_RESOURCE_NEEDS_REBUILD) { + displayPipelines = new ArrayList<>(cvkSwapChain.GetImageCount()); + ret = CreatePipelines(cvkSwapChain.GetRenderPassHandle(), displayPipelines); + if (VkFailed(ret)) { return ret; } + } + } + + return ret; + } + + + // ========================> Tasks <======================== \\ + // NOTE: we effectively have two levels of staging. The second level is pretty + // straightforward, we need the resources we render with to be in the most + // optimal memory possible: resident GPU memory. This memory cannot be written + // by the CPU so we can't update it directly, instead we update staging buffers + // that are both GPU and CPU writable then copy from that into the final GPU + // buffers. + // The first level of staging is required as our staging buffers are VkDevice + // resources and the device may not be initialised when these tasks are called. + // This is the case when a graph is loaded into a new tab, we need to be able + // to process the tasks that load the graph vertices etc before the device is + // ready to create staging buffers. For this reason we update local arrays + // in the BuildArray functions, then copy these to our staging buffers + // during the renderer's display loop (when the rendering lambda of each task + // is executed). This also means we don't need to synchronise these arrays + // created by the visual processor's thread with the staging buffers that have + // a lifespan entirely within the rendering thread (AWT event thread). This + // is possible because the arrays are locals in the task functions and aren't + // modified by the visual processor thread after they've been added to the + // queue of tasks for the renderer to process. + + private Vertex[] BuildVertexArray(final VisualAccess access, int first, int last) { + final int newVertexCount = (last - first) + 1; + if (newVertexCount > 0) { + List vertices = new ArrayList<>(); + for (int pos = first; pos <= last; ++pos) { + if (access.getBlazed(pos)) { + vertices.add(new Vertex(access.getBlazeColor(pos), + access.getVertexVisibility(pos), + pos, + access.getBlazeAngle(pos))); + } + } + + Vertex[] verticesCopy = new Vertex[vertices.size()]; + return vertices.toArray(verticesCopy); + } else { + return null; + } + } + + private void RebuildVertexStagingBuffer(Vertex[] vertices) { + vertexCount = (vertices != null ? vertices.length : 0); + final int newSizeBytes = vertexCount * Vertex.BYTES; + final boolean recreate = cvkVertexStagingBuffer == null || newSizeBytes != cvkVertexStagingBuffer.GetBufferSize(); + + if (recreate) { + if (cvkVertexStagingBuffer != null) { + cvkVertexStagingBuffer.Destroy(); + cvkVertexStagingBuffer = null; + } + + if (newSizeBytes > 0) { + cvkVertexStagingBuffer = CVKBuffer.Create(newSizeBytes, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKLoopsRenderable.RebuildVertexStagingBuffer cvkVertexStagingBuffer"); + } + } + + if (newSizeBytes > 0) { + UpdateVertexStagingBuffer(vertices, 0, vertices.length - 1); + } + } + + private void UpdateVertexStagingBuffer(Vertex[] vertices, int first, int last) { + CVKAssertNotNull(cvkVertexStagingBuffer); + CVKAssertNotNull(vertices != null); + CVKAssert(vertices.length > 0 && vertices.length > (last - first)); + CVKAssert(last >= 0 && last >= first && first >= 0); + + int offset = first * Vertex.BYTES; + int size = ((last - first) + 1) * Vertex.BYTES; + + ByteBuffer pMemory = cvkVertexStagingBuffer.StartMemoryMap(offset, size); + for (Vertex vertex : vertices) { + vertex.CopyToSequentially(pMemory); + } + cvkVertexStagingBuffer.EndMemoryMap(); + pMemory = null; // now unmapped, do not use + } + + public CVKRenderUpdateTask TaskUpdateBlazes(final VisualChange change, final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + final int changedVerticeRange[]; + final Vertex vertices[]; + if (cvkVertexStagingBuffer == null || change.isEmpty()) { + vertices = BuildVertexArray(access, 0, access.getVertexCount() - 1); + changedVerticeRange = null; + } else { + changedVerticeRange = change.getRange(); + vertices = BuildVertexArray(access, changedVerticeRange[0], changedVerticeRange[1]); + } + + GetLogger().fine("TaskUpdateBlazes frame %d: (%d) blazed verts", cvkVisualProcessor.GetFrameNumber(), vertices != null ? vertices.length : 0); + + // We have to enumerate the vertices to see which are blazed before we can compare the old and new buffer sizes + final boolean rebuildRequired = cvkVertexStagingBuffer == null || + change.isEmpty() || + vertices.length * Vertex.BYTES != cvkVertexStagingBuffer.GetBufferSize(); + + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + if (rebuildRequired) { + RebuildVertexStagingBuffer(vertices); + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + vertexCount = vertices != null ? vertices.length : 0; + } else if (vertexBuffersState != CVK_RESOURCE_NEEDS_REBUILD) { + UpdateVertexStagingBuffer(vertices, changedVerticeRange[0], changedVerticeRange[1]); + SetVertexBuffersState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + public CVKRenderUpdateTask TaskUpdateSizeAndOpacity(final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + final float updatedBlazeSize = access.getBlazeSize(); + final float updatedBlazeOpacity = access.getBlazeOpacity(); + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + geometryUBO.scale = updatedBlazeSize; + fragmentUBO.opacity = updatedBlazeOpacity; + + if (geometryUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + + if (fragmentUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetFragmentUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + public CVKRenderUpdateTask TaskUpdateCamera() { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + UpdateVertexPushConstants(); + }; + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKFPSRenderable.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKFPSRenderable.java new file mode 100644 index 0000000000..f291cc8aee --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKFPSRenderable.java @@ -0,0 +1,1076 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.renderables; + +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.*; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.system.MemoryUtil.*; +import static org.lwjgl.vulkan.VK10.*; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKIconTextureAtlas; +import au.gov.asd.tac.constellation.utilities.camera.Graphics3DUtilities; +import au.gov.asd.tac.constellation.utilities.graphics.Matrix44f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector3f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector4f; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool.CVKDescriptorPoolRequirements; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import java.nio.ByteBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkVertexInputAttributeDescription; +import org.lwjgl.vulkan.VkVertexInputBindingDescription; +import au.gov.asd.tac.constellation.visual.vulkan.CVKVisualProcessor; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_CLEAN; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_NEEDS_REBUILD; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_NEEDS_UPDATE; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKBuffer; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKCommandBuffer; +import java.nio.LongBuffer; +import java.util.ArrayList; +import java.util.List; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkCommandBufferInheritanceInfo; +import org.lwjgl.vulkan.VkDescriptorImageInfo; +import org.lwjgl.vulkan.VkDescriptorSetAllocateInfo; +import org.lwjgl.vulkan.VkDescriptorSetLayoutBinding; +import org.lwjgl.vulkan.VkDescriptorSetLayoutCreateInfo; +import org.lwjgl.vulkan.VkPipelineLayoutCreateInfo; +import org.lwjgl.vulkan.VkWriteDescriptorSet; +import org.lwjgl.vulkan.VkDescriptorBufferInfo; +import org.lwjgl.vulkan.VkPushConstantRange; + + +public class CVKFPSRenderable extends CVKRenderable { + private static final int MAX_DIGITS = 4; + private static final int ICON_BITS = 16; + private static final int ICON_MASK = 0xffff; + private static final int DIGIT_ICON_OFFSET = 4; + private static final int FPS_OFFSET = 50; + + private final Vector3f bottomRightCorner = new Vector3f(); + private float pyScale = 0; + private float pxScale = 0; + private final Vertex[] vertices = new Vertex[MAX_DIGITS]; + private final VertexPushConstantObject vertexUBO = new VertexPushConstantObject(); + private final GeometryUniformBufferObject geometryUBO = new GeometryUniformBufferObject(); + private LongBuffer pDescriptorSets = null; + private List currentFPS = null; + + // TODO: Candidates to be moved to CVKRenderable + private List geometryUniformBuffers = null; + private List vertexBuffers = null; + private List displayCommandBuffers = null; + private CVKBuffer cvkStagingBuffer = null; + private CVKBuffer cvkGeomUBStagingBuffer = null; + + // Cache image view and sampler handles so we know when they've been recreated + // so we can recreate our descriptors + private long hAtlasSampler = VK_NULL_HANDLE; + private long hAtlasImageView = VK_NULL_HANDLE; + + private ByteBuffer vertexPushConstants = null; + private static final int MAX_SAMPLES = 100; + private int numSamples = 0; + private int currentSample = 0; + private int numDigits = 0; + private int lastFPS = 0; + private long lastNanoTime = 0; + private long frameNanoTimes[] = new long[MAX_SAMPLES]; + private int digitIconIndices[] = new int[10]; + + + // ========================> Classes <======================== \\ + + private static class Vertex { + // This looks a little weird for Java, but LWJGL and JOGL both require + // contiguous memory which is passed to the native GL or VK libraries. + private static final int BYTES = 2 * Integer.BYTES + Vector4f.BYTES; + private static final int OFFSETOF_DATA = 0; + private static final int OFFSET_BKGCLR = 2 * Integer.BYTES; + private static final int BINDING = 0; + + private int[] data = new int[2]; + private Vector4f backgroundIconColor = new Vector4f(); + + public Vertex(int[] inData, Vector4f inColour) { + data = inData; + backgroundIconColor = inColour; + } + + private static void CopyTo(ByteBuffer buffer, Vertex[] vertices) { + for(Vertex vertex : vertices) { + buffer.putInt(vertex.data[0]); + buffer.putInt(vertex.data[1]); + + buffer.putFloat(vertex.backgroundIconColor.a[0]); + buffer.putFloat(vertex.backgroundIconColor.a[1]); + buffer.putFloat(vertex.backgroundIconColor.a[2]); + buffer.putFloat(vertex.backgroundIconColor.a[3]); + } + } + + /** + * A VkVertexInputBindingDescription defines the rate at which data is + * consumed by vertex shader (per vertex or per instance). + * The input rate determines whether to move to the next data entry after + * each vertex or after each instance. + * The binding description also defines the vertex stride, the number of + * bytes that must be stepped from vertex n-1 to vertex n. + * + * @return Binding description for the FPS vertex type + */ + private static VkVertexInputBindingDescription.Buffer GetBindingDescription() { + + VkVertexInputBindingDescription.Buffer bindingDescription = + VkVertexInputBindingDescription.callocStack(1); + + // If we bind multiple vertex buffers with different descriptions + // this is the index of this description occupies in the array of + // bound descriptions. + bindingDescription.binding(BINDING); + bindingDescription.stride(Vertex.BYTES); + bindingDescription.inputRate(VK_VERTEX_INPUT_RATE_VERTEX); + + return bindingDescription; + } + + + /** + * A VkVertexInputAttributeDescription describes each element int the + * vertex buffer. + * binding: matches the binding member of VkVertexInputBindingDescription + * location: corresponds to the layout(location = #) in the vertex shader + * for this element (0 for data, 1 for bkgClr). + * format: format the shader will interpret this as. + * offset: bytes from the start of the vertex this attribute starts at + * + * @return + */ + private static VkVertexInputAttributeDescription.Buffer GetAttributeDescriptions() { + + VkVertexInputAttributeDescription.Buffer attributeDescriptions = + VkVertexInputAttributeDescription.callocStack(2); + + // data + VkVertexInputAttributeDescription posDescription = attributeDescriptions.get(0); + posDescription.binding(BINDING); + posDescription.location(0); + posDescription.format(VK_FORMAT_R32G32_SINT); + posDescription.offset(OFFSETOF_DATA); + + // backgroundIconColor + VkVertexInputAttributeDescription colorDescription = attributeDescriptions.get(1); + colorDescription.binding(BINDING); + colorDescription.location(1); + colorDescription.format(VK_FORMAT_R32G32B32A32_SFLOAT); + colorDescription.offset(OFFSET_BKGCLR); + + return attributeDescriptions.rewind(); + } + } + + @Override + protected VkVertexInputBindingDescription.Buffer GetVertexBindingDescription() { + return Vertex.GetBindingDescription(); + } + + @Override + protected VkVertexInputAttributeDescription.Buffer GetVertexAttributeDescriptions() { + return Vertex.GetAttributeDescriptions(); + } + + private static class VertexPushConstantObject { + private static final int BYTES = Matrix44f.BYTES + 2 * Float.BYTES; + + public Matrix44f mvMatrix; + public float visibilityLow = 0; + public float visibilityHigh = 0; + + + public VertexPushConstantObject() { + mvMatrix = new Matrix44f(); + } + + private void CopyTo(ByteBuffer buffer) { + PutMatrix44f(buffer, mvMatrix); + buffer.putFloat(visibilityLow); + buffer.putFloat(visibilityHigh); + } + } + + private static class GeometryUniformBufferObject { + private final Matrix44f pMatrix = new Matrix44f(); + private float pixelDensity = 0; + private float pScale = 0; + private int iconsPerRowColumn; + private int iconsPerLayer; + private int atlas2DDimension; + private static Integer padding = null; + + private static int SizeOf() { + if (padding == null) { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + final int minAlignment = CVKDevice.GetMinUniformBufferAlignment(); + + // The matrices are 64 bytes each so should line up on a boundary (unless the minimum alignment is huge) + CVKAssert(minAlignment <= (16 * Float.BYTES)); + + int sizeof = 16 * Float.BYTES + // pMatrix + 1 * Float.BYTES + // pixelDensity + 1 * Float.BYTES + // pScale + 1 * Integer.BYTES + // iconsPerRowColumn + 1 * Integer.BYTES + // iconsPerLayer + 1 * Integer.BYTES; // atlas2DDimension + + final int overrun = sizeof % minAlignment; + padding = Integer.valueOf(overrun > 0 ? minAlignment - overrun : 0); + } + + return 16 * Float.BYTES + // pMatrix + 1 * Float.BYTES + // pixelDensity + 1 * Float.BYTES + // pScale + 1 * Integer.BYTES + // iconsPerRowColumn + 1 * Integer.BYTES + // iconsPerLayer + 1 * Integer.BYTES + // atlas2DDimension + padding; + } + + private void CopyTo(ByteBuffer buffer) { + PutMatrix44f(buffer, pMatrix); + buffer.putFloat(pixelDensity); + buffer.putFloat(pScale); + buffer.putInt(iconsPerRowColumn); + buffer.putInt(iconsPerLayer); + buffer.putInt(atlas2DDimension); + + for (int i = 0; i < padding; ++i) { + buffer.put((byte)0); + } + } + } + + + // ========================> Shaders <======================== \\ + + @Override + protected String GetVertexShaderName() { return "SimpleIcon.vs"; } + + @Override + protected String GetGeometryShaderName() { return "SimpleIcon.gs"; } + + @Override + protected String GetFragmentShaderName() { return "SimpleIcon.fs"; } + + + // ========================> Lifetime <======================== \\ + + public CVKFPSRenderable(CVKVisualProcessor visualProcessor) { + super(visualProcessor); + + depthWrite = false; + currentFPS = new ArrayList<>(); + currentFPS.add(0); + currentFPS.add(0); + currentFPS.add(0); + currentFPS.add(0); + } + + @Override + public int Initialise() { + int ret = super.Initialise(); + if (VkFailed(ret)) { return ret; } + + ret = CreateStagingBuffer(); + if (VkFailed(ret)) { return ret; } + + ret = CreateDescriptorLayout(); + if (VkFailed(ret)) { return ret; } + + ret = CreatePipelineLayout(); + if (VkFailed(ret)) { return ret; } + + for (int digit = 0; digit < 10; ++digit) { + // Returns the index of the icon, not a success code + digitIconIndices[digit] = CVKIconTextureAtlas.GetInstance().AddIcon(String.format("Character.%d", digit)); + } + + ret = CreatePushConstants(); + if (VkFailed(ret)) { return ret; } + + return ret; + } + + @Override + public void Destroy() { + DestroyVertexBuffers(); + DestroyGeometryUniformBuffers(); + DestroyDescriptorSets(); + DestroyDescriptorLayout(); + DestroyCommandBuffers(); + DestroyPipelines(); + DestroyPipelineLayout(); + DestroyPushConstants(); + DestroyStagingBuffer(); + + CVKAssertNull(displayPipelines); + CVKAssertNull(hPipelineLayout); + CVKAssertNull(pDescriptorSets); + CVKAssertNull(geometryUniformBuffers); + CVKAssertNull(vertexBuffers); + CVKAssertNull(displayCommandBuffers); + CVKAssertNull(vertexPushConstants); + CVKAssertNull(cvkStagingBuffer); + } + + + // ========================> Swap chain <======================== \\ + + @Override + protected int DestroySwapChainResources(){ + CVKAssertNotNull(cvkSwapChain); + + cvkVisualProcessor.VerifyInRenderThread(); + + int ret = VK_SUCCESS; + + // We only need to recreate these resources if the number of images in + // the swapchain changes or if this is the first call after the initial + // swapchain is created. + if (displayPipelines != null && swapChainImageCountChanged) { + DestroyVertexBuffers(); + DestroyGeometryUniformBuffers(); + DestroyDescriptorSets(); + DestroyCommandBuffers(); + DestroyPipelines(); + DestroyCommandBuffers(); + + CVKAssertNull(displayPipelines); + CVKAssertNull(pDescriptorSets); + CVKAssertNull(geometryUniformBuffers); + CVKAssertNull(vertexBuffers); + CVKAssertNull(displayCommandBuffers); + } + + cvkSwapChain = null; + return ret; + } + + + // ========================> Staging buffers <======================== \\ + + private int CreateStagingBuffer() { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + + // Maximum vertex count is fixed so create the staging buffer here + int size = vertices.length * Vertex.BYTES; + cvkStagingBuffer = CVKBuffer.Create(size, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKFPSRenderable cvkStagingBuffer"); + + size = GeometryUniformBufferObject.SizeOf(); + cvkGeomUBStagingBuffer = CVKBuffer.Create(size, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKFPSRenderable.cvkGeomUBStagingBuffer"); + + return VK_SUCCESS; + } + + private void DestroyStagingBuffer() { + if (cvkStagingBuffer != null) { + cvkStagingBuffer.Destroy(); + cvkStagingBuffer = null; + } + if (cvkGeomUBStagingBuffer != null) { + cvkGeomUBStagingBuffer.Destroy(); + cvkGeomUBStagingBuffer = null; + } + } + + + // ========================> Vertex buffers <======================== \\ + + private int CreateVertexBuffers(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + + int imageCount = cvkSwapChain.GetImageCount(); + vertexBuffers = new ArrayList<>(); + + // Size to upper limit, we don't have to draw each one. + int size = vertices.length * Vertex.BYTES; + + // TODO: most if not all of Constellation's vertex buffers won't change after creation + // so they should probably be allocated as VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT and staged + // to once to fill them (staging buffer this is host visible then copied to the device local) + for (int i = 0; i < imageCount; ++i) { + CVKBuffer cvkVertexBuffer = CVKBuffer.Create(size, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKFPSRenderable cvkVertexBuffer %d", i)); + vertexBuffers.add(cvkVertexBuffer); + } + + // Populate them with some values + UpdateVertexBuffers(stack); + + return ret; + } + + private int UpdateVertexBuffers(MemoryStack stack) { + int ret = VK_SUCCESS; + + // Size to upper limit, we don't have to draw each one. + for (int i = 0; i < vertices.length; ++i) { + int data[] = new int[2]; + + int digit = currentFPS.get(i); + + final int foregroundIconIndex; + if (digit >= 0 && digit < 10) { + foregroundIconIndex = CVKIconTextureAtlas.GetInstance().AddIcon(Integer.toString(digit)); + } else { + foregroundIconIndex = digit; + } + + final int backgroundIconIndex = CVKIconTextureAtlas.TRANSPARENT_ICON_INDEX; + + // packed icon indices + data[0] = (backgroundIconIndex << ICON_BITS) | (foregroundIconIndex & ICON_MASK); + + // offset which is used for this digit's position in SimpleIcon.vs + data[1] = i * DIGIT_ICON_OFFSET; + + // colour which is inexplicably converted to a 4x4 matrix in the vert shader + Vector4f colour = new Vector4f(1.0f,1.0f,1.0f,1.0f); + + vertices[i] = new Vertex(data, colour); + } + + // Copy to our staging buffer (host read/write) + int size = vertices.length * Vertex.BYTES; + PointerBuffer data = stack.mallocPointer(1); + vkMapMemory(CVKDevice.GetVkDevice(), cvkStagingBuffer.GetMemoryBufferHandle(), 0, size, 0, data); + { + Vertex.CopyTo(data.getByteBuffer(0, size), vertices); + } + vkUnmapMemory(CVKDevice.GetVkDevice(), cvkStagingBuffer.GetMemoryBufferHandle()); + + // Populate + for (int i = 0; i < vertexBuffers.size(); ++i) { + CVKBuffer cvkVertexBuffer = vertexBuffers.get(i); + cvkVertexBuffer.CopyFrom(cvkStagingBuffer); + } + + SetVertexBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public int GetVertexCount(){ return numDigits; } + + private void DestroyVertexBuffers() { + if (null != vertexBuffers) { + vertexBuffers.forEach(el -> {el.Destroy();}); + vertexBuffers.clear(); + vertexBuffers = null; + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + } + } + + + // ========================> Uniform buffers <======================== \\ + + private int CreateGeometryUniformBuffers(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + + int imageCount = cvkSwapChain.GetImageCount(); + + geometryUniformBuffers = new ArrayList<>(); + for (int i = 0; i < imageCount; ++i) { + CVKBuffer geomUniformBuffer = CVKBuffer.Create(GeometryUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKFPSRenderable geomUniformBuffer %d", i)); + geometryUniformBuffers.add(geomUniformBuffer); + } + + return UpdateGeometryUniformBuffers(stack); + } + + private int UpdateGeometryUniformBuffers(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + + // LIFTED FROM FPSRenderable.reshape(...) + //TT: the logic here seems to be the FPS text needs to be 50 pixels from the + // edges, the calculation of dx and dy implies that the viewport is + //-width/2, -height/2, width/2, height/2 + + // whenever the drawable shape changes, recalculate the place where the fps is drawn + + // This is a GL viewport where the screen space origin is in the bottom left corner + //final int[] viewport = new int[]{0, 0, cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight()}; + + // In Vulkan the screen space origin is in the top left hand corner. Note we put the origin at 0, H and + // the viewport dimensions are W and -H. The -H means we we still have a 0->H range, just running in the + // opposite direction to GL. + final int[] viewport = cvkSwapChain.GetViewport(); + + final int dx = cvkSwapChain.GetWidth() / 2 - FPS_OFFSET; + final int dy = cvkSwapChain.GetHeight() / 2 - FPS_OFFSET; + pxScale = calculateXProjectionScale(viewport); + pyScale = calculateYProjectionScale(viewport); + Graphics3DUtilities.moveByProjection(ZERO_3F, IDENTITY_44F, viewport, dx, dy, bottomRightCorner); + + // set the number of pixels per world unit at distance 1 + geometryUBO.pixelDensity = cvkVisualProcessor.GetPixelDensity(); + geometryUBO.pScale = pyScale; + geometryUBO.iconsPerRowColumn = CVKIconTextureAtlas.GetInstance().serializableData.iconsPerRowColumn; + geometryUBO.iconsPerLayer = CVKIconTextureAtlas.GetInstance().serializableData.iconsPerLayer; + geometryUBO.atlas2DDimension = CVKIconTextureAtlas.GetInstance().serializableData.texture2DDimension; + geometryUBO.pMatrix.set(cvkVisualProcessor.GetProjectionMatrix()); + + // Fill of the geometry uniform buffer + PointerBuffer pData = stack.mallocPointer(1); + int size = GeometryUniformBufferObject.SizeOf(); + ret = vkMapMemory(CVKDevice.GetVkDevice(), cvkGeomUBStagingBuffer.GetMemoryBufferHandle(), 0, size, 0, pData); + if (VkFailed(ret)) { return ret; } + { + geometryUBO.CopyTo(pData.getByteBuffer(0, size)); + } + vkUnmapMemory(CVKDevice.GetVkDevice(), cvkGeomUBStagingBuffer.GetMemoryBufferHandle()); + + // Copy the UBOs in VK buffers we can bind to a descriptor set + final int imageCount = cvkSwapChain.GetImageCount(); + for (int i = 0; i < imageCount; ++i) { + geometryUniformBuffers.get(i).CopyFrom(cvkGeomUBStagingBuffer); + } + + // LIFTED FROM FPSRenerable.display(...) + // Initialise source data to sensible values + final Matrix44f scalingMatrix = new Matrix44f(); + scalingMatrix.makeScalingMatrix(pxScale, pyScale, 0); + final Matrix44f srMatrix = new Matrix44f(); + srMatrix.multiply(scalingMatrix, IDENTITY_44F); + + // build the fps matrix by translating the sr matrix + final Matrix44f translationMatrix = new Matrix44f(); + translationMatrix.makeTranslationMatrix(bottomRightCorner.getX(), + bottomRightCorner.getY(), + bottomRightCorner.getZ()); + vertexUBO.mvMatrix.multiply(translationMatrix, srMatrix); + + // In the JOGL version these were in a static var CAMERA that never changed + vertexUBO.visibilityLow = cvkVisualProcessor.getDisplayCamera().getVisibilityLow(); + vertexUBO.visibilityHigh = cvkVisualProcessor.getDisplayCamera().getVisibilityHigh(); + + // Update the push constants data + vertexUBO.CopyTo(vertexPushConstants); + vertexPushConstants.flip(); + + SetGeometryUniformBufferState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private void DestroyGeometryUniformBuffers() { + if (geometryUniformBuffers != null) { + geometryUniformBuffers.forEach(el -> {el.Destroy();}); + geometryUniformBuffers = null; + + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_REBUILD); + } + } + + + // ========================> Push constants <======================== \\ + + private int CreatePushConstants() { + // Initialise push constants to identity mtx + vertexPushConstants = memAlloc(VertexPushConstantObject.BYTES); + PutMatrix44f(vertexPushConstants, IDENTITY_44F); + vertexPushConstants.putFloat(0.0f); + vertexPushConstants.putFloat(1.0f); + vertexPushConstants.flip(); + + return VK_SUCCESS; + } + + + private void DestroyPushConstants() { + if (vertexPushConstants != null) { + memFree(vertexPushConstants); + vertexPushConstants = null; + } + } + + + // ========================> Command buffers <======================== \\ + + public int CreateCommandBuffers(){ + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + int imageCount = cvkSwapChain.GetImageCount(); + + displayCommandBuffers = new ArrayList<>(imageCount); + + for (int i = 0; i < imageCount; ++i) { + CVKCommandBuffer buffer = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_SECONDARY, + GetLogger(), + String.format("CVKFPSRenderable %d", i)); + displayCommandBuffers.add(buffer); + } + + return ret; + } + + @Override + public VkCommandBuffer GetDisplayCommandBuffer(int imageIndex) { + return displayCommandBuffers.get(imageIndex).GetVKCommandBuffer(); + } + + @Override + public int RecordDisplayCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int imageIndex){ + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(CVKDevice.GetCommandPoolHandle()); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + CVKCommandBuffer commandBuffer = displayCommandBuffers.get(imageIndex); + + ret = commandBuffer.BeginRecordSecondary(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, + inheritanceInfo); + if (VkFailed(ret)) { return ret; } + + commandBuffer.SetViewPort(cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight()); + commandBuffer.SetScissor(cvkVisualProcessor.GetCanvas().GetCurrentSurfaceExtent()); + + commandBuffer.BindGraphicsPipeline(displayPipelines.get(imageIndex)); + commandBuffer.BindVertexInput(vertexBuffers.get(imageIndex).GetBufferHandle()); + + commandBuffer.BindGraphicsDescriptorSets(hPipelineLayout, pDescriptorSets.get(imageIndex)); + commandBuffer.PushConstants(hPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, vertexPushConstants); + + commandBuffer.Draw(GetVertexCount()); + + ret = commandBuffer.FinishRecord(); + if (VkFailed(ret)) { return ret; } + + return ret; + } + + private void DestroyCommandBuffers() { + if (null != displayCommandBuffers) { + displayCommandBuffers.forEach(el -> {el.Destroy();}); + displayCommandBuffers.clear(); + displayCommandBuffers = null; + } + } + + + // ========================> Descriptors <======================== \\ + + private int CreateDescriptorLayout() { + int ret; + + try(MemoryStack stack = stackPush()) { + /* + Vertex shader is updated via push constants + Geometry shader needs a different uniform buffer. + Fragment shader needs a sampler2Darray + */ + + VkDescriptorSetLayoutBinding.Buffer bindings = VkDescriptorSetLayoutBinding.callocStack(2, stack); + + VkDescriptorSetLayoutBinding geomUBOLayout = bindings.get(1); + geomUBOLayout.binding(0); + geomUBOLayout.descriptorCount(1); + geomUBOLayout.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + geomUBOLayout.pImmutableSamplers(null); + geomUBOLayout.stageFlags(VK_SHADER_STAGE_GEOMETRY_BIT); + + VkDescriptorSetLayoutBinding samplerLayoutBinding = bindings.get(0); + samplerLayoutBinding.binding(1); + samplerLayoutBinding.descriptorCount(1); + samplerLayoutBinding.descriptorType(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + samplerLayoutBinding.pImmutableSamplers(null); + samplerLayoutBinding.stageFlags(VK_SHADER_STAGE_FRAGMENT_BIT); + + VkDescriptorSetLayoutCreateInfo layoutInfo = VkDescriptorSetLayoutCreateInfo.callocStack(stack); + layoutInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO); + layoutInfo.pBindings(bindings); + + LongBuffer pDescriptorSetLayout = stack.mallocLong(1); + + ret = vkCreateDescriptorSetLayout(CVKDevice.GetVkDevice(), layoutInfo, null, pDescriptorSetLayout); + if (VkSucceeded(ret)) { + hDescriptorLayout = pDescriptorSetLayout.get(0); + GetLogger().info("CVKFPSRenderable created hDescriptorLayout: 0x%016X", hDescriptorLayout); + } + } + return ret; + } + + private void DestroyDescriptorLayout() { + GetLogger().info("CVKFPSRenderable destroying hDescriptorLayout: 0x%016X", hDescriptorLayout); + vkDestroyDescriptorSetLayout(CVKDevice.GetVkDevice(), hDescriptorLayout, null); + hDescriptorLayout = VK_NULL_HANDLE; + } + + private int CreateDescriptorSets(MemoryStack stack) { + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + // The same layout is used for each descriptor set (each descriptor set is + // identical but allow the GPU and CPU to desynchronise. + final int imageCount = cvkSwapChain.GetImageCount(); + LongBuffer layouts = stack.mallocLong(imageCount); + for (int i = 0; i < imageCount; ++i) { + layouts.put(i, hDescriptorLayout); + } + + VkDescriptorSetAllocateInfo allocInfo = VkDescriptorSetAllocateInfo.callocStack(stack); + allocInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO); + allocInfo.descriptorPool(cvkDescriptorPool.GetDescriptorPoolHandle()); + allocInfo.pSetLayouts(layouts); + + // Allocate the descriptor sets from the descriptor pool, they'll be unitialised + pDescriptorSets = MemoryUtil.memAllocLong(imageCount); + ret = vkAllocateDescriptorSets(CVKDevice.GetVkDevice(), allocInfo, pDescriptorSets); + if (VkFailed(ret)) { return ret; } + + for (int i = 0; i < pDescriptorSets.capacity(); ++i) { + GetLogger().info("CVKFPSRenderable allocated hDescriptorSet %d: 0x%016X", i, pDescriptorSets.get(i)); + } + + return UpdateDescriptorSets(stack); + } + + private int UpdateDescriptorSets(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(CVKIconTextureAtlas.GetInstance().GetAtlasImageViewHandle()); + CVKAssertNotNull(CVKIconTextureAtlas.GetInstance().GetAtlasSamplerHandle()); + + int ret = VK_SUCCESS; + + int imageCount = cvkSwapChain.GetImageCount(); + + // Struct for the size of the uniform buffer used by SimpleIcon.gs (we fill the actual buffer below) + VkDescriptorBufferInfo.Buffer geometryUniformBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + geometryUniformBufferInfo.offset(0); + geometryUniformBufferInfo.range(GeometryUniformBufferObject.SizeOf()); + + // Struct for the size of the image sampler used by SimpleIcon.fs + VkDescriptorImageInfo.Buffer imageInfo = VkDescriptorImageInfo.callocStack(1, stack); + imageInfo.imageLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + imageInfo.imageView(CVKIconTextureAtlas.GetInstance().GetAtlasImageViewHandle()); + imageInfo.sampler(CVKIconTextureAtlas.GetInstance().GetAtlasSamplerHandle()); + + // We need 2 write descriptors, 1 for the geometry stage uniform buffer and one for fragment stage texture + VkWriteDescriptorSet.Buffer descriptorWrites = VkWriteDescriptorSet.callocStack(2, stack); + + VkWriteDescriptorSet geomUBDescriptorWrite = descriptorWrites.get(0); + geomUBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + geomUBDescriptorWrite.dstBinding(0); + geomUBDescriptorWrite.dstArrayElement(0); + geomUBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + geomUBDescriptorWrite.descriptorCount(1); + geomUBDescriptorWrite.pBufferInfo(geometryUniformBufferInfo); + + VkWriteDescriptorSet samplerDescriptorWrite = descriptorWrites.get(1); + samplerDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + samplerDescriptorWrite.dstBinding(1); + samplerDescriptorWrite.dstArrayElement(0); + samplerDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + samplerDescriptorWrite.descriptorCount(1); + samplerDescriptorWrite.pImageInfo(imageInfo); + + + for (int i = 0; i < imageCount; ++i) { + long descriptorSet = pDescriptorSets.get(i); + + geometryUniformBufferInfo.buffer(geometryUniformBuffers.get(i).GetBufferHandle()); + geomUBDescriptorWrite.dstSet(descriptorSet); + samplerDescriptorWrite.dstSet(descriptorSet); + + // Update the descriptors with a write and no copy + GetLogger().info("CVKFPSRenderable updating descriptorSet: 0x%016X", descriptorSet); + vkUpdateDescriptorSets(CVKDevice.GetVkDevice(), descriptorWrites, null); + } + + // Cache atlas handles so we know when to recreate descriptors + hAtlasSampler = CVKIconTextureAtlas.GetInstance().GetAtlasSamplerHandle(); + hAtlasImageView = CVKIconTextureAtlas.GetInstance().GetAtlasImageViewHandle(); + + SetDescriptorSetsState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public void IncrementDescriptorTypeRequirements(CVKDescriptorPoolRequirements reqs, CVKDescriptorPoolRequirements perImageReqs) { + // SimpleIcon.gs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]; + // SimpleIcon.fs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER]; + + // One set per image + ++perImageReqs.poolDesciptorSetCount; + } + + private int DestroyDescriptorSets() { + int ret = VK_SUCCESS; + + if (pDescriptorSets != null) { + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(cvkDescriptorPool.GetDescriptorPoolHandle()); + GetLogger().fine("CVKFPSRenderable returning %d descriptor sets to the pool", pDescriptorSets.capacity()); + + for (int i = 0; i < pDescriptorSets.capacity(); ++i) { + GetLogger().info("CVKFPSRenderable freeing hDescriptorSet %d: 0x%016X", i, pDescriptorSets.get(i)); + } + + // After calling vkFreeDescriptorSets, all descriptor sets in pDescriptorSets are invalid. + ret = vkFreeDescriptorSets(CVKDevice.GetVkDevice(), cvkDescriptorPool.GetDescriptorPoolHandle(), pDescriptorSets); + pDescriptorSets = null; + checkVKret(ret); + } + + SetDescriptorSetsState(CVK_RESOURCE_NEEDS_REBUILD); + + return ret; + } + + @Override + protected int DestroyDescriptorPoolResources() { + int ret = VK_SUCCESS; + + if (cvkDescriptorPool != null) { + return DestroyDescriptorSets(); + } + + return ret; + } + + + // ========================> Pipelines <======================== \\ + + private int CreatePipelineLayout() { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(hDescriptorLayout); + + int ret; + try (MemoryStack stack = stackPush()) { + VkPushConstantRange.Buffer pushConstantRange; + pushConstantRange = VkPushConstantRange.callocStack(1, stack); + pushConstantRange.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + pushConstantRange.size(VertexPushConstantObject.BYTES); + pushConstantRange.offset(0); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = VkPipelineLayoutCreateInfo.callocStack(stack); + pipelineLayoutInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO); + pipelineLayoutInfo.pSetLayouts(stack.longs(hDescriptorLayout)); + pipelineLayoutInfo.pPushConstantRanges(pushConstantRange); + LongBuffer pPipelineLayout = stack.longs(VK_NULL_HANDLE); + ret = vkCreatePipelineLayout(CVKDevice.GetVkDevice(), pipelineLayoutInfo, null, pPipelineLayout); + if (VkFailed(ret)) { return ret; } + hPipelineLayout = pPipelineLayout.get(0); + CVKAssertNotNull(hPipelineLayout); + } + return ret; + } + + private void DestroyPipelineLayout() { + if (hPipelineLayout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(CVKDevice.GetVkDevice(), hPipelineLayout, null); + hPipelineLayout = VK_NULL_HANDLE; + } + } + + + // ========================> Display <======================== \\ + + @Override + public boolean NeedsDisplayUpdate() { + cvkVisualProcessor.VerifyInRenderThread(); + + return true; + } + + @Override + public int DisplayUpdate() { + int ret = VK_SUCCESS; + cvkVisualProcessor.VerifyInRenderThread(); + + UpdateFPS(); + + if (hAtlasSampler != CVKIconTextureAtlas.GetInstance().GetAtlasSamplerHandle() || + hAtlasImageView != CVKIconTextureAtlas.GetInstance().GetAtlasImageViewHandle()) { + if (geometryUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + if (descriptorSetsState != CVK_RESOURCE_NEEDS_REBUILD) { + SetDescriptorSetsState(CVK_RESOURCE_NEEDS_UPDATE); + } + } + + try (MemoryStack stack = stackPush()) { + if (geometryUniformBufferState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateGeometryUniformBuffers(stack); + if (VkFailed(ret)) { return ret; } + } else if (geometryUniformBufferState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateGeometryUniformBuffers(stack); + if (VkFailed(ret)) { return ret; } + } + + if (descriptorSetsState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateDescriptorSets(stack); + if (VkFailed(ret)) { return ret; } + } else if (descriptorSetsState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateDescriptorSets(stack); + if (VkFailed(ret)) { return ret; } + } + + // Update VBs as the FPS changes constantly as we render, + if (vertexBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateVertexBuffers(stack); + if (VkFailed(ret)) { return ret; } + } else if (vertexBuffersState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateVertexBuffers(stack); + if (VkFailed(ret)) { return ret; } + } + + if (commandBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateCommandBuffers(); + if (VkFailed(ret)) { return ret; } + } + + if (pipelinesState == CVK_RESOURCE_NEEDS_REBUILD) { + displayPipelines = new ArrayList<>(cvkSwapChain.GetImageCount()); + ret = CreatePipelines(cvkSwapChain.GetRenderPassHandle(), displayPipelines); + if (VkFailed(ret)) { return ret; } + } + } + + return ret; + } + + + // ========================> Helpers <======================== \\ + + // LIFTED FROM FPSRenderable.java + private float calculateXProjectionScale(final int[] viewport) { + // calculate the number of pixels a cvkScene object of y-length 1 projects to. + final Vector4f proj1 = new Vector4f(); + //TT: Projects 0,0,0 into an identity matrix scaled to the width and + // height of the viewport (in pixels) and a z of 0->1. This will lead to + // proj1 being width/2, height/2, 0.5 + Graphics3DUtilities.project(ZERO_3F, IDENTITY_44F, viewport, proj1); + final Vector4f proj2 = new Vector4f(); + final Vector3f unitPosition = new Vector3f(1, 0, 0); + //TT: Projecting 1,0,0 into the same space yields width, height/2, 0.5 + Graphics3DUtilities.project(unitPosition, IDENTITY_44F, viewport, proj2); + //TT: the above seems like a lot of messing around to arrive at + // xScale = width/2 + final float xScale = proj2.getX() - proj1.getX(); + //TT: 4/(width/2), what are the 256 and 64? Magic numbers rock. Maybe + // dimensions of the generated icon texture? 8/width. + // 256 is the icon width and height in the atlas texture, 64 is the number + // of icons you can fit in a 2048x2048 texture. + return (256.0f / 64) / xScale; + } + + private float calculateYProjectionScale(final int[] viewport) { + // calculate the number of pixels a scene object of y-length 1 projects to. + final Vector4f proj1 = new Vector4f(); + Graphics3DUtilities.project(ZERO_3F, IDENTITY_44F, viewport, proj1); + final Vector4f proj2 = new Vector4f(); + final Vector3f unitPosition = new Vector3f(0, 1, 0); + Graphics3DUtilities.project(unitPosition, IDENTITY_44F, viewport, proj2); + final float yScale = proj2.getY() - proj1.getY(); + return (256.0f / 64) / yScale; + } + + private void UpdateFPS() { + long now = System.nanoTime(); + + // If this is the first frame, mark the start time and return + if (numSamples == 0 && lastNanoTime == 0) { + lastNanoTime = now; + return; + } + + // Elapsed nanoseconds(ish) since the last update + long elapsedNanos = now - lastNanoTime; + lastNanoTime = now; + frameNanoTimes[currentSample] = elapsedNanos; + + // Advance the sample and count + currentSample = (currentSample + 1) % MAX_SAMPLES; + numSamples = numSamples == MAX_SAMPLES ? MAX_SAMPLES : (numSamples + 1); + + // Calculate the average + long sumNanoTimes = 0; + for (int i = 0; i < numSamples; ++i) { + sumNanoTimes += frameNanoTimes[i]; + } + double averageNanoTime = (double)sumNanoTimes / (double)numSamples; + + // There are one billion nanoseconds in a second so + // that's the denominator to figure out how many frames we're getting + // per second over the number of samples we've selected. NB: instantaneous + // FPS/frame times are great for spotting spikes but an average is + // better for guaging overall performance. + int fps = (int)(1000000000.0 / averageNanoTime); + if (fps != lastFPS && vertexBuffersState != CVK_RESOURCE_NEEDS_REBUILD) { + SetVertexBuffersState(CVK_RESOURCE_NEEDS_UPDATE); + } + lastFPS = fps; + + // Update the characters to display + int[] fpsDigits = Long.toString(fps).chars().map(c -> c -= '0').toArray(); + if (fpsDigits.length == 0) { + numDigits = 1; + currentFPS.set(0, digitIconIndices[0]); + } else if (fpsDigits.length > 4) { + numDigits = 4; + currentFPS.set(0, digitIconIndices[9]); + currentFPS.set(1, digitIconIndices[9]); + currentFPS.set(2, digitIconIndices[9]); + currentFPS.set(3, digitIconIndices[9]); + } else { + numDigits = fpsDigits.length; + for (int i = 0; i < fpsDigits.length; ++i) { + currentFPS.add(i, fpsDigits[i]); + } + } + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKHitTester.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKHitTester.java new file mode 100644 index 0000000000..3e428a3155 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKHitTester.java @@ -0,0 +1,498 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.renderables; + +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.*; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.*; +import static org.lwjgl.vulkan.VK10.*; +import static org.lwjgl.system.MemoryStack.stackPush; +import au.gov.asd.tac.constellation.graph.hittest.HitState; +import au.gov.asd.tac.constellation.graph.hittest.HitState.HitType; +import au.gov.asd.tac.constellation.graph.hittest.HitTestRequest; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool.CVKDescriptorPoolRequirements; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import au.gov.asd.tac.constellation.visual.vulkan.CVKSwapChain; +import au.gov.asd.tac.constellation.visual.vulkan.CVKVisualProcessor; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKCommandBuffer; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKImage; +import java.nio.LongBuffer; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.LinkedBlockingDeque; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkCommandBufferInheritanceInfo; +import org.lwjgl.vulkan.VkFramebufferCreateInfo; +import org.lwjgl.vulkan.VkVertexInputAttributeDescription; +import org.lwjgl.vulkan.VkVertexInputBindingDescription; + + +/** + * This lives in the Core Interactive Graph project so it can import HitTestRequest and + * HitState. If it lived in Core Display Vulkan importing those types would require + * Core Display Vulkan to be dependent on Core Interactive Graph which would be a + * circular dependency. + * + * + * The Hit Tester performs a second offscreen render pass with objects that need + * to be tested against mouse clicks/mouse overs (e.g. Icons, Connections). + * However the objects are each drawn with a unique color determined in the shader + * by the node id. + *

+ * Whenever the mouse is moved, read the current pixel from the alternate + * image and convert the unique color back to the node id. + *

+ * It is assumed that node ids are >=0. Since a black background would return + * 0, we add 1 to the node id in the shader, and subtract 1 here. Change this + * for a non-black background. + *

+ * The alternate framebuffer is currently R32F format. This gives 22 bits of + * mantissa, or 4,194,304 ids. Using the sign bit gives another 22 bits. We use + * positive numbers for node ids, negative numbers for line ids. + */ +public class CVKHitTester extends CVKRenderable { + private HitTestRequest hitTestRequest; + private final BlockingDeque requestQueue = new LinkedBlockingDeque<>(); + private final Queue> notificationQueues = new LinkedList<>(); + private boolean needsDisplayUpdate = true; + private CVKImage cvkImage = null; + private CVKImage cvkDepthImage = null; + private Long hFrameBufferHandle = null; + private CVKCommandBuffer commandBuffer = null; + private final int colorFormat = VK_FORMAT_R32_SFLOAT; // Only use the red channel for hit testing + + private CVKRenderableResourceState frameBuffersState = CVK_RESOURCE_CLEAN; + private CVKRenderableResourceState imagesState = CVK_RESOURCE_CLEAN; + + + // ========================> Debuggering <======================== \\ + + static int counter = 0; + private void SaveToFile() { + counter++; + + if (counter % 100 == 0 ) { + // Debug code to write out the offscreen hittester image to file + String fileName = String.format("C:\\OffscreenRender_%d.png", counter); + cvkImage.SaveToFile(fileName); + } + } + + private void SetFrameBuffersState(final CVKRenderableResourceState state) { + CVKAssert(!(frameBuffersState == CVK_RESOURCE_NEEDS_REBUILD && state == CVK_RESOURCE_NEEDS_UPDATE)); + if (LOGSTATECHANGE) { + GetLogger().info("%d\t frameBuffersState %s -> %s\tSource: %s", + cvkVisualProcessor.GetFrameNumber(), frameBuffersState.name(), state.name(), GetParentMethodName()); + } + frameBuffersState = state; + } + private void SetImagesState(final CVKRenderableResourceState state) { + CVKAssert(!(imagesState == CVK_RESOURCE_NEEDS_REBUILD && state == CVK_RESOURCE_NEEDS_UPDATE)); + if (LOGSTATECHANGE) { + GetLogger().info("%d\t imagesState %s -> %s\tSource: %s", + cvkVisualProcessor.GetFrameNumber(), imagesState.name(), state.name(), GetParentMethodName()); + } + imagesState = state; + } + + + // ========================> Lifetime <======================== \\ + + public CVKHitTester(CVKVisualProcessor visualProcessor) { + super(visualProcessor); + } + + @Override + public int Initialise() { + return super.Initialise(); + } + + @Override + public void Destroy() { + DestroyFrameBuffer(); + DestroyImage(); + DestroyCommandBuffer(); + + CVKAssertNull(hFrameBufferHandle); + CVKAssertNull(cvkImage); + CVKAssertNull(commandBuffer); + } + + + // ========================> Swap chain <======================== \\ + + @Override + protected int DestroySwapChainResources() { + this.cvkSwapChain = null; + + // We only need to recreate these resources if the number of images in + // the swapchain changes or if this is the first call after the initial + // swapchain is created. + if (swapChainImageCountChanged) { + DestroyCommandBuffer(); + } + + // Unlike other renderables we have resources that are tied to the size + // of the display buffer so they must be recreated even when the image + // count doesn't change. + DestroyImage(); + DestroyFrameBuffer(); + + return VK_SUCCESS; + } + + @Override + public int SetNewSwapChain(CVKSwapChain newSwapChain) { + int ret = super.SetNewSwapChain(newSwapChain); + if (VkFailed(ret)) { return ret; } + + if (swapChainImageCountChanged) { + // The number of images has changed, we need to rebuild all image + // buffered resources + SetCommandBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + } + + // Unlike other renderables we have resources that are tied to the size + // of the display buffer so they must be recreated even when the image + // count doesn't change. + SetImagesState(CVK_RESOURCE_NEEDS_REBUILD); + SetFrameBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + + return ret; + } + + + // ========================> Image <======================== \\ + + private int CreateImages() { + CVKAssertNotNull(cvkSwapChain); + CVKAssert(cvkSwapChain.GetDepthFormat() != VK_FORMAT_UNDEFINED); + CVKAssertNull(cvkImage); + CVKAssertNull(cvkDepthImage); + + int ret = VK_SUCCESS; + int textureWidth = cvkSwapChain.GetWidth(); + int textureHeight = cvkSwapChain.GetHeight(); + int requiredLayers = 1; + + // Create destination color image to render to + cvkImage = CVKImage.Create( textureWidth, + textureHeight, + requiredLayers, + colorFormat, // R32 Float - we only use the Red channel for hit testing + VK_IMAGE_VIEW_TYPE_2D, + VK_IMAGE_TILING_LINEAR, // Linear Tiling so we can read it later + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT , // Host Visible so we can read the memory without transitioning + VK_IMAGE_ASPECT_COLOR_BIT, + null, + GetLogger(), + "CVKHitTester cvkImage"); + if (cvkImage == null) { + return CVK_ERROR_HITTEST_SOURCE_IMAGE_CREATE_FAILED; + } + + // Create depth image required for hittesting + cvkDepthImage = CVKImage.Create(textureWidth, + textureHeight, + requiredLayers, + cvkSwapChain.GetDepthFormat(), + VK_IMAGE_VIEW_TYPE_2D, + VK_IMAGE_TILING_OPTIMAL, + VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + VK_IMAGE_ASPECT_DEPTH_BIT, + null, + GetLogger(), + "CVKHitTester cvkDepthImage"); // TODO - aspect mask + if (cvkDepthImage == null) { + return CVK_ERROR_HITTEST_DEPTH_IMAGE_CREATE_FAILED; + } + + SetImagesState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private void DestroyImage() { + if (cvkImage != null) { + cvkImage.Destroy(); + cvkImage = null; + } + + if (cvkDepthImage != null) { + cvkDepthImage.Destroy(); + cvkDepthImage = null; + } + } + + + // ========================> Frame buffer <======================== \\ + + private int CreateFrameBuffer() { + int ret = VK_SUCCESS; + + try(MemoryStack stack = stackPush()) { + VkFramebufferCreateInfo framebufferInfo = VkFramebufferCreateInfo.callocStack(stack); + framebufferInfo.sType(VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO); + framebufferInfo.renderPass(cvkSwapChain.GetOffscreenRenderPassHandle()); + framebufferInfo.width(cvkSwapChain.GetWidth()); + framebufferInfo.height(cvkSwapChain.GetHeight()); + framebufferInfo.layers(1); + + LongBuffer attachments = stack.mallocLong(2); + LongBuffer pFramebuffer = stack.mallocLong(1); + + attachments.put(0, cvkImage.GetImageViewHandle()); + attachments.put(1, cvkDepthImage.GetImageViewHandle()); + framebufferInfo.pAttachments(attachments); + + ret = vkCreateFramebuffer(CVKDevice.GetVkDevice(), + framebufferInfo, + null, //allocation callbacks + pFramebuffer); + if (VkFailed(ret)) { return ret; } + + hFrameBufferHandle = pFramebuffer.get(0); + + SetFrameBuffersState(CVK_RESOURCE_CLEAN); + + } + return ret; + + } + + private void DestroyFrameBuffer() { + if (hFrameBufferHandle != null) { + vkDestroyFramebuffer(CVKDevice.GetVkDevice(), hFrameBufferHandle, null); + hFrameBufferHandle = null; + GetLogger().info("Destroyed frame buffer for HitTester"); + } + } + + + // ========================> Vertex buffers <======================== \\ + + @Override + public int GetVertexCount() { return 0; } + + + // ========================> Command buffers <======================== \\ + + private int CreateCommandBuffer() { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNull(commandBuffer); + + int ret = VK_SUCCESS; + + commandBuffer = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_PRIMARY, GetLogger(), "CVKHitTester CommandBuffer"); + + SetCommandBuffersState(CVK_RESOURCE_CLEAN); + GetLogger().info("Init Command Buffer - HitTester"); + + return ret; + } + + @Override + public VkCommandBuffer GetDisplayCommandBuffer(int imageIndex) { return commandBuffer.GetVKCommandBuffer(); } + + @Override + public int RecordDisplayCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int index) { + return VK_SUCCESS; + } + + private void DestroyCommandBuffer() { + if (commandBuffer != null) { + commandBuffer.Destroy(); + commandBuffer = null; + } + } + + // ========================> Descriptors <======================== \\ + + @Override + public int DestroyDescriptorPoolResources() { return VK_SUCCESS; } + + @Override + public void IncrementDescriptorTypeRequirements(CVKDescriptorPoolRequirements reqs, CVKDescriptorPoolRequirements perImageReqs) {} + + + // ========================> Display <======================== \\ + + @Override + public boolean NeedsDisplayUpdate() { + return needsDisplayUpdate || + imagesState != CVK_RESOURCE_CLEAN || + frameBuffersState != CVK_RESOURCE_CLEAN || + commandBuffersState != CVK_RESOURCE_CLEAN; + } + + @Override + public int DisplayUpdate() { + int ret = VK_SUCCESS; + + if (imagesState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateImages(); + if (VkFailed(ret)) { return ret; } + } + if (commandBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateCommandBuffer(); + if (VkFailed(ret)) { return ret; } + } + if (frameBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateFrameBuffer(); + if (VkFailed(ret)) { return ret; } + } + + ServiceHitRequests(); + + needsDisplayUpdate = false; + return ret; + } + + public synchronized void ServiceHitRequests() { + // Hold off on servicing until we have a usable pick buffer + if (imagesState != CVK_RESOURCE_CLEAN) { + return; + } + + try { + if (requestQueue != null && !requestQueue.isEmpty()) { + requestQueue.forEach(request -> notificationQueues.add(request.getNotificationQueue())); + hitTestRequest = requestQueue.getLast(); + requestQueue.clear(); + } + + if (!notificationQueues.isEmpty()) { + final int x = hitTestRequest.getX(); + final int y = hitTestRequest.getY(); + int redPixel = 0; + + if (cvkImage.GetLayout() != VK_IMAGE_LAYOUT_UNDEFINED) { + redPixel = cvkImage.ReadPixel(x, y); + } + + final int id; + final HitType currentHitType; + if (redPixel == 0) { + currentHitType = HitType.NO_ELEMENT; + id = -1; + } else { + currentHitType = redPixel > 0 ? HitType.VERTEX : HitType.TRANSACTION; + id = redPixel > 0 ? redPixel - 1 : -redPixel - 1; + } + + final HitState hitState = hitTestRequest.getHitState(); + hitState.setCurrentHitId(id); + hitState.setCurrentHitType(currentHitType); + if (hitTestRequest.getFollowUpOperation() != null) { + hitTestRequest.getFollowUpOperation().accept(hitState); + } + synchronized (this.notificationQueues) { + while (!notificationQueues.isEmpty()) { + final Queue queue = notificationQueues.remove(); + if (queue != null) { + queue.add(hitState); + } + } + } + } + } finally { + + } + } + + + public int OffscreenRender(List hitTestRenderables) { + cvkVisualProcessor.VerifyInRenderThread(); + + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(CVKDevice.GetCommandPoolHandle()); + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + if (hitTestRenderables.isEmpty()) { + return ret; + } + + try (MemoryStack stack = stackPush()) { + + ret = commandBuffer.Begin(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT); + if (VkFailed(ret)) { return ret; } + + // Pre Draw Barrier + commandBuffer.PipelineImageMemoryBarrier(cvkImage.GetImageHandle(), + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, // Old/New Layout + 0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, // Src/Dst Access mask + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, // Src/Dst Stage mask + 0, 1); // baseMipLevel/mipLevelCount + + commandBuffer.BeginRenderPass(cvkSwapChain.GetOffscreenRenderPassHandle(), + hFrameBufferHandle, cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight(), + 1, 1, VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS); + + // Inheritance info for the secondary command buffers (same for all!) + VkCommandBufferInheritanceInfo inheritanceInfo = VkCommandBufferInheritanceInfo.callocStack(stack); + inheritanceInfo.sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO); + inheritanceInfo.pNext(0); + inheritanceInfo.framebuffer(hFrameBufferHandle); + inheritanceInfo.renderPass(cvkSwapChain.GetOffscreenRenderPassHandle()); + inheritanceInfo.occlusionQueryEnable(false); + inheritanceInfo.queryFlags(0); + inheritanceInfo.pipelineStatistics(0); + + // Loop through command buffers of hit test objects and record their buffers + hitTestRenderables.forEach(renderable -> { + if (renderable.GetVertexCount() > 0) { + renderable.RecordHitTestCommandBuffer(inheritanceInfo, 0); + vkCmdExecuteCommands(commandBuffer.GetVKCommandBuffer(), renderable.GetHitTestCommandBuffer(0)); + } + }); + + commandBuffer.EndRenderPass(); + + // Pre Draw Barrier + commandBuffer.PipelineImageMemoryBarrier(cvkImage.GetImageHandle(), + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, // Old/New Layout + 0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, // Src/Dst Access mask + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, // Src/Dst Stage mask + 0, 1); // baseMipLevel/mipLevelCount + + cvkImage.SetLayout(VK_IMAGE_LAYOUT_GENERAL); + commandBuffer.EndAndSubmit(); + } + + return ret; + } + + + // ========================> Tasks <======================== \\ + + public void queueRequest(final HitTestRequest request) { + requestQueue.add(request); + needsDisplayUpdate = true; + } + + + // ========================> Helpers <======================== \\ + @Override + protected VkVertexInputBindingDescription.Buffer GetVertexBindingDescription() { return null; } + + @Override + protected VkVertexInputAttributeDescription.Buffer GetVertexAttributeDescriptions() { return null; } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKIconLabelsRenderable.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKIconLabelsRenderable.java new file mode 100644 index 0000000000..9f9d7cd9ce --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKIconLabelsRenderable.java @@ -0,0 +1,1530 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.renderables; + +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.*; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.*; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.system.MemoryUtil.*; +import static org.lwjgl.vulkan.VK10.*; +import au.gov.asd.tac.constellation.utilities.color.ConstellationColor; +import au.gov.asd.tac.constellation.utilities.glyphs.GlyphManager; +import au.gov.asd.tac.constellation.utilities.glyphs.GlyphStreamContext; +import au.gov.asd.tac.constellation.utilities.glyphs.NodeGlyphStreamContext; +import au.gov.asd.tac.constellation.utilities.graphics.Matrix44f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector3f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector4f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector4i; +import au.gov.asd.tac.constellation.utilities.text.LabelUtilities; +import au.gov.asd.tac.constellation.utilities.visual.VisualAccess; +import au.gov.asd.tac.constellation.utilities.visual.VisualChange; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool.CVKDescriptorPoolRequirements; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import au.gov.asd.tac.constellation.visual.vulkan.CVKRenderUpdateTask; +import au.gov.asd.tac.constellation.visual.vulkan.CVKSwapChain; +import au.gov.asd.tac.constellation.visual.vulkan.CVKVisualProcessor; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKBuffer; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKCommandBuffer; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKGlyphTextureAtlas; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils; +import java.nio.ByteBuffer; +import java.nio.LongBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkCommandBufferInheritanceInfo; +import org.lwjgl.vulkan.VkDescriptorBufferInfo; +import org.lwjgl.vulkan.VkDescriptorImageInfo; +import org.lwjgl.vulkan.VkDescriptorSetAllocateInfo; +import org.lwjgl.vulkan.VkDescriptorSetLayoutBinding; +import org.lwjgl.vulkan.VkDescriptorSetLayoutCreateInfo; +import org.lwjgl.vulkan.VkPipelineLayoutCreateInfo; +import org.lwjgl.vulkan.VkPushConstantRange; +import org.lwjgl.vulkan.VkVertexInputAttributeDescription; +import org.lwjgl.vulkan.VkVertexInputBindingDescription; +import org.lwjgl.vulkan.VkWriteDescriptorSet; + +/** + * Requirements: + * - labels on connections (these can be multiline) + * - labels above or below icons + * - icon labels can be in any language + * - icon label determined by attribute 'identifier', can be multiline + * - icon label top determined by graph array attribute 'node_labels_top' (array of attributes, first 4 displayed) + * - icon label bottom determined by graph array attribute 'node_labels_bottom' + * - appears only 1 font at present, SansSerif 64 Plain. This is likely configurable + */ + +/** + * What resources do node labels have + * - vs: vertex push constant (camera matrix) + * 0 vs: vertex ubo (label meta data) + * 1 vs: xyzw buffer sampler (icon positions owned by CVKIconsRenderable) + * 2 gs: geometry ubo (proj mtx and render data) + * 3 gs: glyphInfoTexture buffer sampler (glyph coordinates) + * 4 fs: atlas texture sampler (rastered glyphs owned by CVKGlyphTextureAtlas) + */ +public class CVKIconLabelsRenderable extends CVKRenderable implements GlyphManager.GlyphStream { + + // Resources recreated with the swap chain (dependent on the image count) + private LongBuffer pDescriptorSets = null; + private List displayCommandBuffers = null; + private List vertexBuffers = null; + private List vertexUniformBuffers = null; + private List geometryUniformBuffers = null; + + private ByteBuffer vertexPushConstants = null; + private final VertexUniformBufferObject vertexUBO = new VertexUniformBufferObject(); + private final GeometryUniformBufferObject geometryUBO = new GeometryUniformBufferObject(); + + // Resources recreated only through user events + private int vertexCount = 0; + private List vertices = null; + private CVKBuffer cvkVertexStagingBuffer = null; + + private CVKBuffer cvkVertexUBStagingBuffer = null; + private CVKBuffer cvkGeometryUBStagingBuffer = null; + private final Vector4i topLabelRowSizes = new Vector4i(); + private final Vector4i bottomLabelRowSizes = new Vector4i(); + private final Vector3f[] topLabelColours = new Vector3f[LabelUtilities.MAX_LABELS_TO_DRAW]; + private final Vector3f[] bottomLabelColours = new Vector3f[LabelUtilities.MAX_LABELS_TO_DRAW]; + + // Resources we don't own but use and must track so we know when to update + // our descriptors + private long hGlyphAtlasSampler = VK_NULL_HANDLE; + private long hGlyphAtlasImageView = VK_NULL_HANDLE; + private long hGlyphCoordinateBuffer = VK_NULL_HANDLE; + private long hGlyphCoordinateBufferView = VK_NULL_HANDLE; + private long hPositionBuffer = VK_NULL_HANDLE; + private long hPositionBufferView = VK_NULL_HANDLE; + + + // ========================> Classes <======================== \\ + + private static class Vertex { + // This looks a little weird for Java, but LWJGL and JOGL both require + // contiguous memory which is passed to the native GL or VK libraries. + private static final int BYTES = Vector3f.BYTES + Vector4i.BYTES; + private static final int OFFSETOF_GLYPH_DATA = 0; + private static final int OFFSETOF_GRAPH_DATA = 3 * Float.BYTES; + private static final int BINDING = 0; + + // [0..1] x and y offsets of this glyph from the top centre of the line of text + // [2] The visibility of this glyph (constant for a node, but easier to pass in the batch). + private final Vector3f glyphLocationData = new Vector3f(); + + // [0] the index of the glyph in the glyphInfoTexture + // [1] The index of the node containg this glyph in the xyzTexture + // [2] The total scale of the lines and their labels up to this point (< 0 if this is a glyph in a bottom label) + // [3] The label number in which this glyph occurs + private final Vector4i graphLocationData = new Vector4i(); + + public Vertex(int glyphIndex, float x, float y, float visibility, int nodeId, int totalScale, int labelNumber) { + glyphLocationData.setX(x); + glyphLocationData.setY(y); + glyphLocationData.setZ(visibility); + graphLocationData.a[0] = glyphIndex; + graphLocationData.a[1] = nodeId; + graphLocationData.a[2] = totalScale; + graphLocationData.a[3] = labelNumber; + } + + public void CopyToSequentially(ByteBuffer buffer) { + buffer.putFloat(glyphLocationData.a[0]); + buffer.putFloat(glyphLocationData.a[1]); + buffer.putFloat(glyphLocationData.a[2]); + buffer.putInt(graphLocationData.a[0]); + buffer.putInt(graphLocationData.a[1]); + buffer.putInt(graphLocationData.a[2]); + buffer.putInt(graphLocationData.a[3]); + } + + /** + * A VkVertexInputBindingDescription defines the rate at which data is + * consumed by vertex shader (per vertex or per instance). + * The input rate determines whether to move to the next data entry after + * each vertex or after each instance. + * The binding description also defines the vertex stride, the number of + * bytes that must be stepped from vertex n-1 to vertex n. + * + * @return Binding description for the FPS vertex type + */ + private static VkVertexInputBindingDescription.Buffer GetBindingDescription() { + + VkVertexInputBindingDescription.Buffer bindingDescription = + VkVertexInputBindingDescription.callocStack(1); + + // If we bind multiple vertex buffers with different descriptions + // this is the index of this description occupies in the array of + // bound descriptions. + bindingDescription.binding(BINDING); + bindingDescription.stride(Vertex.BYTES); + bindingDescription.inputRate(VK_VERTEX_INPUT_RATE_VERTEX); + + return bindingDescription; + } + + /** + * A VkVertexInputAttributeDescription describes each element int the + * vertex buffer. + * binding: matches the binding member of VkVertexInputBindingDescription + * location: corresponds to the layout(location = #) in the vertex shader + * for this element (0 for data, 1 for bkgClr). + * format: format the shader will interpret this as. + * offset: bytes from the start of the vertex this attribute starts at + * + * @return + */ + private static VkVertexInputAttributeDescription.Buffer GetAttributeDescriptions() { + + VkVertexInputAttributeDescription.Buffer attributeDescriptions = + VkVertexInputAttributeDescription.callocStack(2); + + // glyphLocationData + VkVertexInputAttributeDescription posDescription = attributeDescriptions.get(0); + posDescription.binding(BINDING); + posDescription.location(0); + posDescription.format(VK_FORMAT_R32G32B32_SFLOAT); + posDescription.offset(OFFSETOF_GLYPH_DATA); + + // graphLocationData + VkVertexInputAttributeDescription colorDescription = attributeDescriptions.get(1); + colorDescription.binding(BINDING); + colorDescription.location(1); + colorDescription.format(VK_FORMAT_R32G32B32A32_SINT); + colorDescription.offset(OFFSETOF_GRAPH_DATA); + + return attributeDescriptions.rewind(); + } + } + + @Override + protected VkVertexInputBindingDescription.Buffer GetVertexBindingDescription() { + return Vertex.GetBindingDescription(); + } + + @Override + protected VkVertexInputAttributeDescription.Buffer GetVertexAttributeDescriptions() { + return Vertex.GetAttributeDescriptions(); + } + + private static class VertexUniformBufferObject { + // Each column is a node (bottom or top) label with the following structure: + // [0..2] rgb colour (note label colours do not habve an alpha) + // [3] label size + private final Matrix44f labelBottomInfo = new Matrix44f(); + private final Matrix44f labelTopInfo = new Matrix44f(); + + // Information from the graph's visual state + private float morphMix = 0; + private float visibilityLow = 0; + private float visibilityHigh = 0; + + // The index of the background glyph in the glyphInfo texture + private int backgroundGlyphIndex = 0; + + // Used to draw the label background. + private final Vector4f backgroundColor = new Vector4f(); + + private static Integer padding1 = null; + private static Integer padding2 = null; + + + private static int SizeOf() { + if (padding1 == null) { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + final int minAlignment = CVKDevice.GetMinUniformBufferAlignment(); + + // The matrices are 64 bytes each so should line up on a boundary (unless the minimum alignment is huge) + CVKAssert(minAlignment <= (16 * Float.BYTES)); + + int sizeof = Matrix44f.BYTES + // labelBottomInfo + Matrix44f.BYTES + // labelTopInfo + 1 * Float.BYTES + // morphMix + 1 * Float.BYTES + // visibilityLow + 1 * Float.BYTES + // visibilityHigh + 1 * Integer.BYTES; // backgroundGlyphIndex + + int overrun = sizeof % minAlignment; + padding1 = overrun > 0 ? minAlignment - overrun : 0; + + sizeof += padding1 + + Vector4f.BYTES; //backgroundColor + + overrun = sizeof % minAlignment; + padding2 = overrun > 0 ? minAlignment - overrun : 0; + + } + + return Matrix44f.BYTES + // labelBottomInfo + Matrix44f.BYTES + // labelTopInfo + 1 * Float.BYTES + // morphMix + 1 * Float.BYTES + // visibilityLow + 1 * Float.BYTES + // visibilityHigh + 1 * Integer.BYTES + // backgroundGlyphIndex + padding1 + + Vector4f.BYTES + //backgroundColor + padding2; + } + + private void CopyTo(ByteBuffer buffer) { + PutMatrix44f(buffer, labelBottomInfo); + PutMatrix44f(buffer, labelTopInfo); + + buffer.putFloat(morphMix); + buffer.putFloat(visibilityLow); + buffer.putFloat(visibilityHigh); + buffer.putInt(backgroundGlyphIndex); + + for (int i = 0; i < padding1; ++i) { + buffer.put((byte)0); + } + + for (int i = 0; i < Vector4f.LENGTH; ++i) { + buffer.putFloat(backgroundColor.a[i]); + } + + for (int i = 0; i < padding2; ++i) { + buffer.put((byte)0); + } + } + } + + private static class GeometryUniformBufferObject { + // Matrix to convert from camera coordinates to scene coordinates. + private final Matrix44f pMatrix = new Matrix44f(); + + // The scaling factor to convert from texture coordinates to world unit coordinates + private float widthScalingFactor; + private float heightScalingFactor; + + // Used to draw the connection indicator on the label background. + private final Vector4f highlightColor = new Vector4f(); + + private static Integer padding1 = null; + private static Integer padding2 = null; + + + private static int SizeOf() { + if (padding1 == null) { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + final int minAlignment = CVKDevice.GetMinUniformBufferAlignment(); + + // The matrices are 64 bytes each so should line up on a boundary (unless the minimum alignment is huge) + CVKAssert(minAlignment <= (16 * Float.BYTES)); + + int sizeof = Matrix44f.BYTES + // pMatrix + 1 * Float.BYTES + // widthScalingFactor + 1 * Float.BYTES; // heightScalingFactor + + int overrun = sizeof % minAlignment; + padding1 = overrun > 0 ? minAlignment - overrun : 0; + + sizeof += padding1 + + Vector4f.LENGTH * Float.BYTES; //highlightColor + + overrun = sizeof % minAlignment; + padding2 = overrun > 0 ? minAlignment - overrun : 0; + } + + return Matrix44f.BYTES + // pMatrix + 1 * Float.BYTES + // widthScalingFactor + 1 * Float.BYTES + // heightScalingFactor + padding1 + + Vector4f.BYTES + //highlightColor + padding2; + } + + private void CopyTo(ByteBuffer buffer) { + PutMatrix44f(buffer, pMatrix); + buffer.putFloat(widthScalingFactor); + buffer.putFloat(heightScalingFactor); + + for (int i = 0; i < padding1; ++i) { + buffer.put((byte)0); + } + + for (int i = 0; i < Vector4f.LENGTH; ++i) { + buffer.putFloat(highlightColor.a[i]); + } + + for (int i = 0; i < padding2; ++i) { + buffer.put((byte)0); + } + } + } + + + // ========================> Shaders <======================== \\ + + @Override + protected String GetVertexShaderName() { return "NodeLabel.vs"; } + + @Override + protected String GetGeometryShaderName() { return "Label.gs"; } + + @Override + protected String GetFragmentShaderName() { return "Label.fs"; } + + + // ========================> Lifetime <======================== \\ + + public CVKIconLabelsRenderable(CVKVisualProcessor visualProcessor) { + super(visualProcessor); + for (int i = 0; i < LabelUtilities.MAX_LABELS_TO_DRAW; ++i) { + topLabelColours[i] = new Vector3f(); + bottomLabelColours[i] = new Vector3f(); + } + } + + private void CreateUBOStagingBuffers() { + cvkVertexUBStagingBuffer = CVKBuffer.Create(VertexUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKIconLabelsRenderable.CreateUBOStagingBuffers cvkVertexUBStagingBuffer"); + cvkGeometryUBStagingBuffer = CVKBuffer.Create(GeometryUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKIconLabelsRenderable.CreateUBOStagingBuffers cvkGeometryUBStagingBuffer"); + } + + @Override + public int Initialise() { + int ret = super.Initialise(); + if (VkFailed(ret)) { return ret; } + + CreatePushConstants(); + + ret = CreateDescriptorLayout(); + if (VkFailed(ret)) { return ret; } + + ret = CreatePipelineLayout(); + if (VkFailed(ret)) { return ret; } + + CreateUBOStagingBuffers(); + + return ret; + } + + private void DestroyStagingBuffers() { + if (cvkVertexStagingBuffer != null) { + cvkVertexStagingBuffer.Destroy(); + cvkVertexStagingBuffer = null; + } + if (cvkVertexUBStagingBuffer != null) { + cvkVertexUBStagingBuffer.Destroy(); + cvkVertexUBStagingBuffer = null; + } + if (cvkGeometryUBStagingBuffer != null) { + cvkGeometryUBStagingBuffer.Destroy(); + cvkGeometryUBStagingBuffer = null; + } + } + + @Override + public void Destroy() { + DestroyVertexBuffers(); + DestroyVertexUniformBuffers(); + DestroyGeometryUniformBuffers(); + DestroyDescriptorSets(); + DestroyDescriptorLayout(); + DestroyPipelines(); + DestroyPipelineLayout(); + DestroyCommandBuffers(); + DestroyStagingBuffers(); + DestroyPushConstants(); + + // Reset our cached handles + hGlyphAtlasSampler = VK_NULL_HANDLE; + hGlyphAtlasImageView = VK_NULL_HANDLE; + hGlyphCoordinateBufferView = VK_NULL_HANDLE; + hPositionBufferView = VK_NULL_HANDLE; + + CVKAssert(vertexBuffers == null); + CVKAssert(vertexUniformBuffers == null); + CVKAssert(geometryUniformBuffers == null); + CVKAssert(pDescriptorSets == null); + CVKAssert(hDescriptorLayout == VK_NULL_HANDLE); + CVKAssert(displayCommandBuffers == null); + CVKAssert(displayPipelines == null); + CVKAssert(hPipelineLayout == VK_NULL_HANDLE); + } + + + // ========================> Swap chain <======================== \\ + + @Override + protected int DestroySwapChainResources() { + this.cvkSwapChain = null; + + // We only need to recreate these resources if the number of images in + // the swapchain changes or if this is the first call after the initial + // swapchain is created. + if (displayPipelines != null && swapChainImageCountChanged) { + DestroyVertexBuffers(); + DestroyVertexUniformBuffers(); + DestroyGeometryUniformBuffers(); + DestroyDescriptorSets(); + DestroyCommandBuffers(); + DestroyPipelines(); + } + + return VK_SUCCESS; + } + + @Override + public int SetNewSwapChain(CVKSwapChain newSwapChain) { + int ret = super.SetNewSwapChain(newSwapChain); + if (VkFailed(ret)) { return ret; } + + if (swapChainImageCountChanged) { + // The number of images has changed, we need to rebuild all image + // buffered resources + SetVertexUniformBufferState(CVK_RESOURCE_NEEDS_REBUILD); + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_REBUILD); + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + SetCommandBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + SetDescriptorSetsState(CVK_RESOURCE_NEEDS_REBUILD); + SetPipelinesState(CVK_RESOURCE_NEEDS_REBUILD); + } else { + // View frustum and projection matrix likely have changed. We don't + // need to rebuild our displayPipelines as the frustum is set by dynamic + // state in RecordDisplayCommandBuffer + if (geometryUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + } + + return ret; + } + + + // ========================> Vertex buffers <======================== \\ + + private int CreateVertexBuffers() { + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + + // We can only create vertex buffers if we have something to put in them + if (cvkVertexStagingBuffer.GetBufferSize() > 0) { + int imageCount = cvkSwapChain.GetImageCount(); + vertexBuffers = new ArrayList<>(); + + for (int i = 0; i < imageCount; ++i) { + CVKBuffer cvkVertexBuffer = CVKBuffer.Create(cvkVertexStagingBuffer.GetBufferSize(), + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKIconLabelsRenderable cvkVertexBuffer %d", i)); + vertexBuffers.add(cvkVertexBuffer); + } + + // Populate them with some values + return UpdateVertexBuffers(); + } + + return ret; + } + + private int UpdateVertexBuffers() { + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssert(cvkVertexStagingBuffer != null); + CVKAssert(vertexBuffers != null); + CVKAssert(vertexBuffers.size() > 0); + CVKAssert(cvkVertexStagingBuffer.GetBufferSize() == vertexBuffers.get(0).GetBufferSize()); + int ret = VK_SUCCESS; + + for (int i = 0; i < vertexBuffers.size(); ++i) { + CVKBuffer cvkVertexBuffer = vertexBuffers.get(i); + ret = cvkVertexBuffer.CopyFrom(cvkVertexStagingBuffer); + if (VkFailed(ret)) { return ret; } + } + + // Note the staging buffer is not freed as we can simplify the update tasks + // by just updating it and then copying it over again during ProcessRenderTasks(). + SetVertexBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public int GetVertexCount() { + return cvkVisualProcessor.GetDrawFlags().drawNodes() && + cvkVisualProcessor.GetDrawFlags().drawNodeLabels() ? + vertexCount : 0; + } + + private void DestroyVertexBuffers() { + if (vertexBuffers != null) { + vertexBuffers.forEach(el -> {el.Destroy();}); + vertexBuffers.clear(); + vertexBuffers = null; + } + } + + + // ========================> Uniform buffers <======================== \\ + + private int CreateVertexUniformBuffers(MemoryStack stack) { + CVKAssert(cvkSwapChain != null); + CVKAssert(vertexUniformBuffers == null); + + vertexUniformBuffers = new ArrayList<>(); + for (int i = 0; i < cvkSwapChain.GetImageCount(); ++i) { + CVKBuffer vertexUniformBuffer = CVKBuffer.Create(VertexUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKIconLabelsRenderable vertexUniformBuffer %d", i)); + vertexUniformBuffers.add(vertexUniformBuffer); + } + return UpdateVertexUniformBuffers(stack); + } + + private int UpdateVertexUniformBuffers(MemoryStack stack) { + CVKAssert(cvkSwapChain != null); + CVKAssert(cvkVertexUBStagingBuffer != null); + CVKAssert(vertexUniformBuffers != null); + CVKAssert(vertexUniformBuffers.size() > 0); + + int ret = VK_SUCCESS; + + // While this never changes we won't know it when the UBO is created as the + // background glyph won't be added until the first render as we need the + // device to be initialised. + // TODO: check this is correct, it comes from the glyphmanager not the atlas + vertexUBO.backgroundGlyphIndex = CVKGlyphTextureAtlas.BACKGROUND_GLYPH_INDEX; + + vertexUBO.morphMix = cvkVisualProcessor.getDisplayCamera().getMix(); + + // TODO: replace with constants. In the JOGL version these were in a static var CAMERA that never changed + vertexUBO.visibilityLow = cvkVisualProcessor.getDisplayCamera().getVisibilityLow(); + vertexUBO.visibilityHigh = cvkVisualProcessor.getDisplayCamera().getVisibilityHigh(); + + // Staging buffer so our VBO can be device local (most performant memory) + ByteBuffer pMemory = cvkVertexUBStagingBuffer.StartMemoryMap(0, VertexUniformBufferObject.SizeOf()); + { + vertexUBO.CopyTo(pMemory); + } + cvkVertexUBStagingBuffer.EndMemoryMap(); + pMemory = null; + + // Copy the staging buffer into the uniform buffer on the device + final int imageCount = cvkSwapChain.GetImageCount(); + for (int i = 0; i < imageCount; ++i) { + ret = vertexUniformBuffers.get(i).CopyFrom(cvkVertexUBStagingBuffer); + if (VkFailed(ret)) { return ret; } + } + + // We are done, reset the resource state + SetVertexUniformBufferState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private void DestroyVertexUniformBuffers() { + if (vertexUniformBuffers != null) { + vertexUniformBuffers.forEach(el -> {el.Destroy();}); + vertexUniformBuffers = null; + } + } + + private int CreateGeometryUniformBuffers(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + CVKAssert(geometryUniformBuffers == null); + + geometryUniformBuffers = new ArrayList<>(); + for (int i = 0; i < cvkSwapChain.GetImageCount(); ++i) { + CVKBuffer geometryUniformBuffer = CVKBuffer.Create(GeometryUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKIconLabelsRenderable geometryUniformBuffer %d", i)); + geometryUniformBuffers.add(geometryUniformBuffer); + } + return UpdateGeometryUniformBuffers(stack); + } + + private int UpdateGeometryUniformBuffers(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkGeometryUBStagingBuffer); + CVKAssertNotNull(geometryUniformBuffers); + CVKAssert(geometryUniformBuffers.size() > 0); + + int ret = VK_SUCCESS; + + geometryUBO.pMatrix.set(cvkVisualProcessor.GetProjectionMatrix()); + geometryUBO.widthScalingFactor = CVKGlyphTextureAtlas.GetInstance().GetWidthScalingFactor(); + geometryUBO.heightScalingFactor = CVKGlyphTextureAtlas.GetInstance().GetWidthScalingFactor(); + + // Staging buffer so our VBO can be device local (most performant memory) + ByteBuffer pMemory = cvkGeometryUBStagingBuffer.StartMemoryMap(0, GeometryUniformBufferObject.SizeOf()); + { + geometryUBO.CopyTo(pMemory); + } + cvkGeometryUBStagingBuffer.EndMemoryMap(); + pMemory = null; + + // Copy the staging buffer into the uniform buffer on the device + final int imageCount = cvkSwapChain.GetImageCount(); + for (int i = 0; i < imageCount; ++i) { + ret = geometryUniformBuffers.get(i).CopyFrom(cvkGeometryUBStagingBuffer); + if (VkFailed(ret)) { return ret; } + } + + // We are done, reset the resource state + SetGeometryUniformBufferState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private void DestroyGeometryUniformBuffers() { + if (geometryUniformBuffers != null) { + geometryUniformBuffers.forEach(el -> {el.Destroy();}); + geometryUniformBuffers = null; + } + } + + + // ========================> Push constants <======================== \\ + + private int CreatePushConstants() { + // Initialise push constants to identity mtx + vertexPushConstants = memAlloc(Matrix44f.BYTES); + PutMatrix44f(vertexPushConstants, IDENTITY_44F); + vertexPushConstants.flip(); + + return VK_SUCCESS; + } + + private void UpdateVertexPushConstants(){ + CVKAssertNotNull(cvkSwapChain); + + vertexPushConstants.clear(); + + // Update MV Matrix + PutMatrix44f(vertexPushConstants, cvkVisualProcessor.getDisplayModelViewMatrix()); + vertexPushConstants.flip(); + } + + private void DestroyPushConstants() { + if (vertexPushConstants != null) { + memFree(vertexPushConstants); + vertexPushConstants = null; + } + } + + + // ========================> Command buffers <======================== \\ + + public int CreateCommandBuffers(){ + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + int imageCount = cvkSwapChain.GetImageCount(); + + displayCommandBuffers = new ArrayList<>(imageCount); + + for (int i = 0; i < imageCount; ++i) { + CVKCommandBuffer buffer = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_SECONDARY, GetLogger(), String.format("CVKIconLabelsRenderable %d", i)); + displayCommandBuffers.add(buffer); + } + + SetCommandBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public VkCommandBuffer GetDisplayCommandBuffer(int imageIndex) { + return displayCommandBuffers.get(imageIndex).GetVKCommandBuffer(); + } + + @Override + public int RecordDisplayCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int imageIndex){ + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(CVKDevice.GetCommandPoolHandle()); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + CVKCommandBuffer commandBuffer = displayCommandBuffers.get(imageIndex); + CVKAssert(commandBuffer != null); + CVKAssert(displayPipelines.get(imageIndex) != null); + + commandBuffer.BeginRecordSecondary(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, inheritanceInfo); + + commandBuffer.SetViewPort(cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight()); + commandBuffer.SetScissor(cvkVisualProcessor.GetCanvas().GetCurrentSurfaceExtent()); + + commandBuffer.BindGraphicsPipeline(displayPipelines.get(imageIndex)); + commandBuffer.BindVertexInput(vertexBuffers.get(imageIndex).GetBufferHandle()); + + // Push MV matrix to the shader + commandBuffer.PushConstants(hPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, vertexPushConstants); + + commandBuffer.BindGraphicsDescriptorSets(hPipelineLayout, pDescriptorSets.get(imageIndex)); + + commandBuffer.Draw(GetVertexCount()); + + ret = commandBuffer.FinishRecord(); + if (VkFailed(ret)) { return ret; } + + return ret; + } + + private void DestroyCommandBuffers() { + if (displayCommandBuffers != null) { + displayCommandBuffers.forEach(el -> {el.Destroy();}); + displayCommandBuffers.clear(); + displayCommandBuffers = null; + SetCommandBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + } + } + + + // ========================> Descriptors <======================== \\ + + private int CreateDescriptorLayout() { + int ret; + + try (MemoryStack stack = stackPush()) { + VkDescriptorSetLayoutBinding.Buffer bindings = VkDescriptorSetLayoutBinding.callocStack(5, stack); + + // 0: Vertex uniform buffer + VkDescriptorSetLayoutBinding vertexUBDSLB = bindings.get(0); + vertexUBDSLB.binding(0); + vertexUBDSLB.descriptorCount(1); + vertexUBDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + vertexUBDSLB.pImmutableSamplers(null); + vertexUBDSLB.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + + // 1: Vertex samplerBuffer (position buffer) + VkDescriptorSetLayoutBinding vertexSamplerDSLB = bindings.get(1); + vertexSamplerDSLB.binding(1); + vertexSamplerDSLB.descriptorCount(1); + vertexSamplerDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + vertexSamplerDSLB.pImmutableSamplers(null); + vertexSamplerDSLB.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + + // 2: Geometry uniform buffer + VkDescriptorSetLayoutBinding geometryUBDSLB = bindings.get(2); + geometryUBDSLB.binding(2); + geometryUBDSLB.descriptorCount(1); + geometryUBDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + geometryUBDSLB.pImmutableSamplers(null); + geometryUBDSLB.stageFlags(VK_SHADER_STAGE_GEOMETRY_BIT); + + // 3: Geometry samplerBuffer (glyph coordinate buffer) + VkDescriptorSetLayoutBinding geometrySamplerDSLB = bindings.get(3); + geometrySamplerDSLB.binding(3); + geometrySamplerDSLB.descriptorCount(1); + geometrySamplerDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + geometrySamplerDSLB.pImmutableSamplers(null); + geometrySamplerDSLB.stageFlags(VK_SHADER_STAGE_GEOMETRY_BIT); + + // 4: Fragment sampler2Darray (atlas) + VkDescriptorSetLayoutBinding fragmentSamplerDSLB = bindings.get(4); + fragmentSamplerDSLB.binding(4); + fragmentSamplerDSLB.descriptorCount(1); + fragmentSamplerDSLB.descriptorType(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + fragmentSamplerDSLB.pImmutableSamplers(null); + fragmentSamplerDSLB.stageFlags(VK_SHADER_STAGE_FRAGMENT_BIT); + + VkDescriptorSetLayoutCreateInfo layoutInfo = VkDescriptorSetLayoutCreateInfo.callocStack(stack); + layoutInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO); + layoutInfo.pBindings(bindings); + + LongBuffer pDescriptorSetLayout = stack.mallocLong(1); + + ret = vkCreateDescriptorSetLayout(CVKDevice.GetVkDevice(), layoutInfo, null, pDescriptorSetLayout); + if (VkSucceeded(ret)) { + hDescriptorLayout = pDescriptorSetLayout.get(0); + GetLogger().info("CVKIconLabelsRenderable created hDescriptorLayout: 0x%016X", hDescriptorLayout); + } + } + return ret; + } + + private void DestroyDescriptorLayout() { + GetLogger().info("CVKIconLabelsRenderable destroying hDescriptorLayout: 0x%016X", hDescriptorLayout); + vkDestroyDescriptorSetLayout(CVKDevice.GetVkDevice(), hDescriptorLayout, null); + hDescriptorLayout = VK_NULL_HANDLE; + } + + private int CreateDescriptorSets(MemoryStack stack) { + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + // The same layout is used for each descriptor set (each descriptor set is + // identical but allow the GPU and CPU to desynchronise. + final int imageCount = cvkSwapChain.GetImageCount(); + LongBuffer layouts = stack.mallocLong(imageCount); + for (int i = 0; i < imageCount; ++i) { + layouts.put(i, hDescriptorLayout); + } + + VkDescriptorSetAllocateInfo allocInfo = VkDescriptorSetAllocateInfo.callocStack(stack); + allocInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO); + allocInfo.descriptorPool(cvkDescriptorPool.GetDescriptorPoolHandle()); + allocInfo.pSetLayouts(layouts); + + // Allocate the descriptor sets from the descriptor pool, they'll be unitialised + pDescriptorSets = MemoryUtil.memAllocLong(imageCount); + ret = vkAllocateDescriptorSets(CVKDevice.GetVkDevice(), allocInfo, pDescriptorSets); + if (VkFailed(ret)) { return ret; } + + for (int i = 0; i < pDescriptorSets.capacity(); ++i) { + GetLogger().info("CVKIconLabelsRenderable allocated hDescriptorSet %d: 0x%016X", i, pDescriptorSets.get(i)); + } + + return UpdateDescriptorSets(stack); + } + + // TODO: do we gain anything by having buffered UBOs? + private int UpdateDescriptorSets(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(pDescriptorSets); + CVKAssert(pDescriptorSets.capacity() > 0); + CVKAssertNotNull(vertexUniformBuffers); + CVKAssert(vertexUniformBuffers.size() > 0); + CVKAssertNotNull(geometryUniformBuffers); + CVKAssert(geometryUniformBuffers.size() > 0); + + int ret = VK_SUCCESS; + + final int imageCount = cvkSwapChain.GetImageCount(); + + // Cache atlas handles so we know when to recreate descriptors + hGlyphAtlasSampler = CVKGlyphTextureAtlas.GetInstance().GetAtlasSamplerHandle(); + hGlyphAtlasImageView = CVKGlyphTextureAtlas.GetInstance().GetAtlasImageViewHandle(); + hGlyphCoordinateBuffer = CVKGlyphTextureAtlas.GetInstance().GetCoordinateBufferHandle(); + hGlyphCoordinateBufferView = CVKGlyphTextureAtlas.GetInstance().GetCoordinateBufferViewHandle(); + hPositionBuffer = cvkVisualProcessor.GetPositionBufferHandle(); + hPositionBufferView = cvkVisualProcessor.GetPositionBufferViewHandle(); + CVKAssertNotNull(hGlyphAtlasSampler); + CVKAssertNotNull(hGlyphAtlasImageView); + CVKAssertNotNull(hGlyphCoordinateBuffer); + CVKAssertNotNull(hGlyphCoordinateBufferView); + CVKAssertNotNull(hPositionBuffer); + CVKAssertNotNull(hPositionBufferView); + + // - Descriptor info structs - + // We create these to describe the different resources we want to address + // in shaders. We have one info struct per resource. We then create a + // write descriptor set structure for each resource for each image. For + // buffered resources like the the uniform buffers we wait to set the + // buffer resource until the image loop below. + + // Struct for the uniform buffer used by NodeLabel.vs + VkDescriptorBufferInfo.Buffer vertexUniformBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + // vertexUniformBufferInfo.buffer is set per imageIndex + vertexUniformBufferInfo.offset(0); + vertexUniformBufferInfo.range(VertexUniformBufferObject.SizeOf()); + + // Struct for texel buffer (positions) used by NodeLabel.vs + VkDescriptorBufferInfo.Buffer positionsTexelBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + positionsTexelBufferInfo.buffer(hPositionBuffer); + positionsTexelBufferInfo.offset(0); + positionsTexelBufferInfo.range(cvkVisualProcessor.GetPositionBufferSize()); + + // Struct for the uniform buffer used by Label.gs + VkDescriptorBufferInfo.Buffer geometryUniformBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + // geometryBufferInfo.buffer is set per imageIndex + geometryUniformBufferInfo.offset(0); + geometryUniformBufferInfo.range(GeometryUniformBufferObject.SizeOf()); + + // Struct for texel buffer (glyph coordinates) used by Label.gs + VkDescriptorBufferInfo.Buffer glyphCoordinateTexelBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + glyphCoordinateTexelBufferInfo.buffer(hGlyphCoordinateBuffer); + glyphCoordinateTexelBufferInfo.offset(0); + glyphCoordinateTexelBufferInfo.range(CVKGlyphTextureAtlas.GetInstance().GetCoordinateBufferSize()); + + // Struct for the size of the image sampler (atlas) used by Label.fs + VkDescriptorImageInfo.Buffer imageInfo = VkDescriptorImageInfo.callocStack(1, stack); + imageInfo.imageLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + imageInfo.imageView(hGlyphAtlasImageView); + imageInfo.sampler(hGlyphAtlasSampler); + + // We need 5 write descriptors, 2 for uniform buffers, 2 for texel buffers and 1 for texture sampler + VkWriteDescriptorSet.Buffer descriptorWrites = VkWriteDescriptorSet.callocStack(5, stack); + + // Vertex uniform buffer + VkWriteDescriptorSet vertexUBDescriptorWrite = descriptorWrites.get(0); + vertexUBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + vertexUBDescriptorWrite.dstBinding(0); + vertexUBDescriptorWrite.dstArrayElement(0); + vertexUBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + vertexUBDescriptorWrite.descriptorCount(1); + vertexUBDescriptorWrite.pBufferInfo(vertexUniformBufferInfo); + + // Vertex texel buffer (positions) + VkWriteDescriptorSet positionsTBDescriptorWrite = descriptorWrites.get(1); + positionsTBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + positionsTBDescriptorWrite.dstBinding(1); + positionsTBDescriptorWrite.dstArrayElement(0); + positionsTBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + positionsTBDescriptorWrite.descriptorCount(1); + positionsTBDescriptorWrite.pBufferInfo(positionsTexelBufferInfo); + positionsTBDescriptorWrite.pTexelBufferView(stack.longs(hPositionBufferView)); + + // Geometry uniform buffer + VkWriteDescriptorSet geometryUBDescriptorWrite = descriptorWrites.get(2); + geometryUBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + geometryUBDescriptorWrite.dstBinding(2); + geometryUBDescriptorWrite.dstArrayElement(0); + geometryUBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + geometryUBDescriptorWrite.descriptorCount(1); + geometryUBDescriptorWrite.pBufferInfo(geometryUniformBufferInfo); + + // Geometry texel buffer (glyph coordinates) + VkWriteDescriptorSet vertexFlagsTBDescriptorWrite = descriptorWrites.get(3); + vertexFlagsTBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + vertexFlagsTBDescriptorWrite.dstBinding(3); + vertexFlagsTBDescriptorWrite.dstArrayElement(0); + vertexFlagsTBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + vertexFlagsTBDescriptorWrite.descriptorCount(1); + vertexFlagsTBDescriptorWrite.pBufferInfo(glyphCoordinateTexelBufferInfo); + vertexFlagsTBDescriptorWrite.pTexelBufferView(stack.longs(hGlyphCoordinateBufferView)); + + // Fragment image (atlas) sampler + VkWriteDescriptorSet atlasSamplerDescriptorWrite = descriptorWrites.get(4); + atlasSamplerDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + atlasSamplerDescriptorWrite.dstBinding(4); + atlasSamplerDescriptorWrite.dstArrayElement(0); + atlasSamplerDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + atlasSamplerDescriptorWrite.descriptorCount(1); + atlasSamplerDescriptorWrite.pImageInfo(imageInfo); + + for (int i = 0; i < imageCount; ++i) { + // Update the buffered resource buffers + vertexUniformBufferInfo.buffer(vertexUniformBuffers.get(i).GetBufferHandle()); + geometryUniformBufferInfo.buffer(geometryUniformBuffers.get(i).GetBufferHandle()); + + // Set the descriptor set we're updating in each write struct + long descriptorSet = pDescriptorSets.get(i); + descriptorWrites.forEach(el -> {el.dstSet(descriptorSet);}); + + // Update the descriptors with a write and no copy + GetLogger().info("CVKIconLabelsRenderable updating descriptorSet: 0x%016X", descriptorSet); + vkUpdateDescriptorSets(CVKDevice.GetVkDevice(), descriptorWrites, null); + } + + SetDescriptorSetsState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private int DestroyDescriptorSets() { + int ret = VK_SUCCESS; + + if (pDescriptorSets != null) { + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(cvkDescriptorPool.GetDescriptorPoolHandle()); + GetLogger().fine("CVKIconLabelsRenderable returning %d descriptor sets to the pool", pDescriptorSets.capacity()); + + for (int i = 0; i < pDescriptorSets.capacity(); ++i) { + GetLogger().info("CVKIconLabelsRenderable freeing hDescriptorSet %d: 0x%016X", i, pDescriptorSets.get(i)); + } + + // After calling vkFreeDescriptorSets, all descriptor sets in pDescriptorSets are invalid. + ret = vkFreeDescriptorSets(CVKDevice.GetVkDevice(), cvkDescriptorPool.GetDescriptorPoolHandle(), pDescriptorSets); + pDescriptorSets = null; + checkVKret(ret); + } + + return ret; + } + + @Override + public int DestroyDescriptorPoolResources() { + int ret = VK_SUCCESS; + + if (cvkDescriptorPool != null) { + return DestroyDescriptorSets(); + } + + return ret; + } + + @Override + public void IncrementDescriptorTypeRequirements(CVKDescriptorPoolRequirements reqs, CVKDescriptorPoolRequirements perImageReqs) { + // NodeLabel.vs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]; + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER]; + + // Label.gs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]; + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER]; + + // Label.fs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER]; + + // One set per image + ++perImageReqs.poolDesciptorSetCount; + } + + @Override + public int SetNewDescriptorPool(CVKDescriptorPool newDescriptorPool) { + int ret = super.SetNewDescriptorPool(newDescriptorPool); + if (VkFailed(ret)) { return ret; } + SetDescriptorSetsState(CVK_RESOURCE_NEEDS_REBUILD); + return ret; + } + + + // ========================> Pipelines <======================== \\ + + private int CreatePipelineLayout() { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(hDescriptorLayout); + + int ret; + try (MemoryStack stack = stackPush()) { + VkPushConstantRange.Buffer pushConstantRange; + pushConstantRange = VkPushConstantRange.calloc(1); + pushConstantRange.get(0).stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + pushConstantRange.get(0).size(64); + pushConstantRange.get(0).offset(0); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = VkPipelineLayoutCreateInfo.callocStack(stack); + pipelineLayoutInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO); + pipelineLayoutInfo.pSetLayouts(stack.longs(hDescriptorLayout)); + pipelineLayoutInfo.pPushConstantRanges(pushConstantRange); + LongBuffer pPipelineLayout = stack.longs(VK_NULL_HANDLE); + ret = vkCreatePipelineLayout(CVKDevice.GetVkDevice(), pipelineLayoutInfo, null, pPipelineLayout); + if (VkFailed(ret)) { return ret; } + hPipelineLayout = pPipelineLayout.get(0); + CVKAssert(hPipelineLayout != VK_NULL_HANDLE); + } + return ret; + } + + private void DestroyPipelineLayout() { + if (hPipelineLayout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(CVKDevice.GetVkDevice(), hPipelineLayout, null); + hPipelineLayout = VK_NULL_HANDLE; + } + } + + + // ========================> Display <======================== \\ + + @Override + public boolean NeedsDisplayUpdate() { + // Race condition: icons owns the position buffer and the update task (on + // the visual processor thread) may not yet be complete. If this happens + // before the first update is finished icons will have a vertexCount of + // 0 so it won't be able to create the position buffer yet. If we can't + // get a handle to the current position buffer then we just skip updating + // until we can. + if (cvkVisualProcessor.GetPositionBufferHandle() == VK_NULL_HANDLE) { + return false; + } + + if (hPositionBuffer != cvkVisualProcessor.GetPositionBufferHandle() || + hPositionBufferView != cvkVisualProcessor.GetPositionBufferViewHandle() || + hGlyphAtlasSampler != CVKGlyphTextureAtlas.GetInstance().GetAtlasSamplerHandle() || + hGlyphAtlasImageView != CVKGlyphTextureAtlas.GetInstance().GetAtlasImageViewHandle() || + hGlyphCoordinateBuffer != CVKGlyphTextureAtlas.GetInstance().GetCoordinateBufferHandle() || + hGlyphCoordinateBufferView != CVKGlyphTextureAtlas.GetInstance().GetCoordinateBufferViewHandle()) { + if (descriptorSetsState != CVK_RESOURCE_NEEDS_REBUILD) { + descriptorSetsState = CVK_RESOURCE_NEEDS_UPDATE; + } + } + return vertexCount > 0 && + (vertexUniformBufferState != CVK_RESOURCE_CLEAN || + geometryUniformBufferState != CVK_RESOURCE_CLEAN || + vertexBuffersState != CVK_RESOURCE_CLEAN || + commandBuffersState != CVK_RESOURCE_CLEAN || + descriptorSetsState != CVK_RESOURCE_CLEAN || + pipelinesState != CVK_RESOURCE_CLEAN); + } + + @Override + public int DisplayUpdate() { + int ret = VK_SUCCESS; + cvkVisualProcessor.VerifyInRenderThread(); + + try (MemoryStack stack = stackPush()) { + // Update vertex buffers + if (vertexBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + DestroyVertexBuffers(); + ret = CreateVertexBuffers(); + if (VkFailed(ret)) { return ret; } + } else if (vertexBuffersState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateVertexBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Vertex uniform buffer (camera guff) + if (vertexUniformBufferState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateVertexUniformBuffers(stack); + if (VkFailed(ret)) { return ret; } + } else if (vertexUniformBufferState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateVertexUniformBuffers(stack); + if (VkFailed(ret)) { return ret; } + } + + // Geometry uniform buffer (projection, highlight colour) + if (geometryUniformBufferState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateGeometryUniformBuffers(stack); + if (VkFailed(ret)) { return ret; } + } else if (geometryUniformBufferState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateGeometryUniformBuffers(stack); + if (VkFailed(ret)) { return ret; } + } + + // Descriptors (binding values to shaders parameters) + if (descriptorSetsState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateDescriptorSets(stack); + if (VkFailed(ret)) { return ret; } + } else if (descriptorSetsState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateDescriptorSets(stack); + if (VkFailed(ret)) { return ret; } + } + + // Command buffers (rendering commands enqueued on the GPU) + if (commandBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateCommandBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Pipelines (all the render state and resources in one object) + if (pipelinesState == CVK_RESOURCE_NEEDS_REBUILD) { + displayPipelines = new ArrayList<>(cvkSwapChain.GetImageCount()); + ret = CreatePipelines(cvkSwapChain.GetRenderPassHandle(), displayPipelines); + if (VkFailed(ret)) { return ret; } + + } + } + + return ret; + } + + + // ========================> Tasks <======================== \\ + + @Override + public void addGlyph(int glyphPosition, float x, float y, GlyphStreamContext streamContext) { + if (streamContext instanceof NodeGlyphStreamContext) { + final NodeGlyphStreamContext context = (NodeGlyphStreamContext) streamContext; + vertices.add(new Vertex(glyphPosition, x, y, context.visibility, + context.currentNodeID, context.totalScale, context.labelNumber)); + } else { + throw new IllegalArgumentException("Provided context lacks Node information, please use a NodeGlyphStreamContext"); + } + } + + @Override + public void newLine(float width, GlyphStreamContext streamContext) { + if (streamContext instanceof NodeGlyphStreamContext) { + final NodeGlyphStreamContext context = (NodeGlyphStreamContext) streamContext; + vertices.add(new Vertex(CVKGlyphTextureAtlas.BACKGROUND_GLYPH_INDEX, -width / 2.0f - 0.2f, 0.0f, context.visibility, + context.currentNodeID, context.totalScale, context.labelNumber)); + } else { + throw new IllegalArgumentException("Provided context lacks Node information, please use a NodeGlyphStreamContext"); + } + } + + private void BufferLabel(final boolean isTop, final int pos, final VisualAccess access, final float visibility) { + int totalScale = LabelUtilities.NRADIUS_TO_LABEL_UNITS; + + if (isTop) { + for (int label = 0; label < access.getTopLabelCount(); label++) { + final String text = access.getVertexTopLabelText(pos, label); + ArrayList lines = LabelUtilities.splitTextIntoLines(text); + Collections.reverse(lines); + for (final String line : lines) { + CVKGlyphTextureAtlas.GetInstance().RenderTextAsLigatures(line, this, new NodeGlyphStreamContext(pos, totalScale, visibility, label)); + totalScale += topLabelRowSizes.a[label]; + } + } + } else { + for (int label = 0; label < access.getBottomLabelCount(); label++) { + final String text = access.getVertexBottomLabelText(pos, label); + ArrayList lines = LabelUtilities.splitTextIntoLines(text); + for (final String line : lines) { + CVKGlyphTextureAtlas.GetInstance().RenderTextAsLigatures(line, this, new NodeGlyphStreamContext(pos, -totalScale, visibility, label)); + totalScale += bottomLabelRowSizes.a[label]; + } + } + } + } + + class BufferLabelWorker extends Thread { + private final boolean isTop; + private final int pos; + private final float visibility; + private final VisualAccess access; + + BufferLabelWorker(final boolean isTop, final int pos, final VisualAccess access, final float visibility) { + this.pos = pos; + this.access = access; + this.visibility = visibility; + this.isTop = isTop; + } + + @Override + public void run() { + BufferLabel(isTop, pos, access, visibility); + } + } + + private void FillLabels(final boolean isTop, final VisualAccess access, final int first, final int last) throws InterruptedException { + final ExecutorService pool = Executors.newFixedThreadPool(CVKUtils.NUM_CORES); + for (int pos = first; pos <= last; ++pos) { + final float visibility = access.getVertexVisibility(pos); + final Runnable thread = new BufferLabelWorker(isTop, pos, access, visibility); + pool.submit(thread); + } + pool.shutdown(); + + pool.awaitTermination(10, TimeUnit.MINUTES); + } + + class FillLabelsWorker extends Thread { + + private final VisualAccess access; + private final int first; + private final int last; + private final boolean isTop; + + FillLabelsWorker(final boolean isTop, final VisualAccess access, final int first, final int last) { + this.access = access; + this.first = first; + this.last = last; + this.isTop = isTop; + } + + @Override + public void run() { + try { + FillLabels(isTop, access, first, last); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + GetLogger().LogException(ex, "Exception thrown from FillTopLabelsWorker"); + } + } + } + + private Vertex[] BuildVertexArray(final VisualAccess access, int first, int last) { + vertices = new ArrayList<>(); + final int newVertexCount = (last - first) + 1; + if (newVertexCount > 0) { + final Thread topLabelThread = new FillLabelsWorker(true, access, first, last); + topLabelThread.start(); + + final Thread bottomLabelThread = new FillLabelsWorker(false, access, first, last); + bottomLabelThread.start(); + + try { + topLabelThread.join(); + bottomLabelThread.join(); + } catch (InterruptedException ex) { + GetLogger().LogException(ex, "Exception thrown from BuildVertexArray"); + } + + Vertex[] verticesCopy = new Vertex[vertices.size()]; + return vertices.toArray(verticesCopy); + } else { + return null; + } + } + + private void RebuildVertexStagingBuffer(Vertex[] vertices) { + vertexCount = (vertices != null ? vertices.length : 0); + final int newSizeBytes = vertexCount * Vertex.BYTES; + final boolean recreate = cvkVertexStagingBuffer == null || newSizeBytes != cvkVertexStagingBuffer.GetBufferSize(); + + if (recreate) { + if (cvkVertexStagingBuffer != null) { + cvkVertexStagingBuffer.Destroy(); + cvkVertexStagingBuffer = null; + } + + if (newSizeBytes > 0) { + cvkVertexStagingBuffer = CVKBuffer.Create(newSizeBytes, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKIconLabelsRenderable.RebuildVertexStagingBuffer cvkVertexStagingBuffer"); + } + } + + if (newSizeBytes > 0) { + UpdateVertexStagingBuffer(vertices, 0, vertices.length - 1); + } + } + + private void UpdateVertexStagingBuffer(Vertex[] vertices, int first, int last) { + CVKAssertNotNull(cvkVertexStagingBuffer); + CVKAssertNotNull(vertices != null); + CVKAssert(vertices.length > 0 && vertices.length > (last - first)); + CVKAssert(last >= 0 && last >= first && first >= 0); + + int offset = first * Vertex.BYTES; + int size = ((last - first) + 1) * Vertex.BYTES; + + ByteBuffer pMemory = cvkVertexStagingBuffer.StartMemoryMap(offset, size); + for (Vertex vertex : vertices) { + vertex.CopyToSequentially(pMemory); + } + cvkVertexStagingBuffer.EndMemoryMap(); + pMemory = null; // now unmapped, do not use + } + + public CVKRenderUpdateTask TaskUpdateLabels(final VisualChange change, final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + cvkVisualProcessor.GetLogger().fine("TaskUpdateLabels frame %d: %d verts", cvkVisualProcessor.GetFrameNumber(), access.getVertexCount()); + + final boolean rebuildRequired = cvkVertexStagingBuffer == null || + access.getVertexCount() * Vertex.BYTES != cvkVertexStagingBuffer.GetBufferSize() || + change.isEmpty(); + final int changedVerticeRange[]; + final Vertex vertexArray[]; + if (rebuildRequired) { + vertexArray = BuildVertexArray(access, 0, access.getVertexCount() - 1); + changedVerticeRange = null; + } else { + changedVerticeRange = change.getRange(); + vertexArray = BuildVertexArray(access, changedVerticeRange[0], changedVerticeRange[1]); + } + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + if (rebuildRequired) { + RebuildVertexStagingBuffer(vertexArray); + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + vertexCount = vertexArray != null ? vertexArray.length : 0; + } else if (vertexBuffersState != CVK_RESOURCE_NEEDS_REBUILD) { + UpdateVertexStagingBuffer(vertexArray, changedVerticeRange[0], changedVerticeRange[1]); + SetVertexBuffersState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + public CVKRenderUpdateTask TaskUpdateColours(final VisualChange change, final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + cvkVisualProcessor.GetLogger().fine("TaskUpdateColours frame %d: %d verts", cvkVisualProcessor.GetFrameNumber(), access.getVertexCount()); + + final int numTopLabels = Math.min(LabelUtilities.MAX_LABELS_TO_DRAW, access.getTopLabelCount()); + for (int i = 0; i < numTopLabels; i++) { + topLabelColours[i].setR(access.getBottomLabelColor(i).getRed()); + topLabelColours[i].setG(access.getBottomLabelColor(i).getGreen()); + topLabelColours[i].setB(access.getBottomLabelColor(i).getBlue()); + } + + final int numBottomLabels = Math.min(LabelUtilities.MAX_LABELS_TO_DRAW, access.getBottomLabelCount()); + for (int i = 0; i < numBottomLabels; i++) { + bottomLabelColours[i].setR(access.getBottomLabelColor(i).getRed()); + bottomLabelColours[i].setG(access.getBottomLabelColor(i).getGreen()); + bottomLabelColours[i].setB(access.getBottomLabelColor(i).getBlue()); + } + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + for (int i = 0; i < numTopLabels; i++) { + vertexUBO.labelTopInfo.setRow(topLabelColours[i], i); + } + for (int i = 0; i < numBottomLabels; i++) { + vertexUBO.labelBottomInfo.setRow(bottomLabelColours[i], i); + } + if (vertexUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetVertexUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + public CVKRenderUpdateTask TaskUpdateSizes(final VisualChange change, final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + cvkVisualProcessor.GetLogger().fine("TaskUpdateSizes frame %d: %d verts", cvkVisualProcessor.GetFrameNumber(), access.getVertexCount()); + + final int numTopLabels = Math.min(LabelUtilities.MAX_LABELS_TO_DRAW, access.getTopLabelCount()); + for (int i = 0; i < numTopLabels; i++) { + topLabelRowSizes.a[i] = (int)(LabelUtilities.NRADIUS_TO_LABEL_UNITS * Math.min(access.getTopLabelSize(i), LabelUtilities.MAX_LABEL_SIZE)); + } + + final int numBottomLabels = Math.min(LabelUtilities.MAX_LABELS_TO_DRAW, access.getBottomLabelCount()); + for (int i = 0; i < numBottomLabels; i++) { + bottomLabelRowSizes.a[i] = (int)(LabelUtilities.NRADIUS_TO_LABEL_UNITS * Math.min(access.getBottomLabelSize(i), LabelUtilities.MAX_LABEL_SIZE)); + } + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + for (int i = 0; i < numTopLabels; i++) { + vertexUBO.labelTopInfo.set(i, 3, topLabelRowSizes.a[i]); + } + for (int i = 0; i < numBottomLabels; i++) { + vertexUBO.labelBottomInfo.set(i, 3, bottomLabelRowSizes.a[i]); + } + if (vertexUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetVertexUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + public CVKRenderUpdateTask TaskSetBackgroundColor(final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + cvkVisualProcessor.GetLogger().fine("TaskSetBackgroundColor frame %d: %d verts", cvkVisualProcessor.GetFrameNumber(), access.getVertexCount()); + final ConstellationColor colour = access.getBackgroundColor(); + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + vertexUBO.backgroundColor.set(colour.getRed(), colour.getGreen(), colour.getBlue(), 1.0f); + if (vertexUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetVertexUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + public CVKRenderUpdateTask TaskSetHighlightColor(final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + cvkVisualProcessor.GetLogger().fine("TaskSetHighlightColor frame %d: %d verts", cvkVisualProcessor.GetFrameNumber(), access.getVertexCount()); + final ConstellationColor colour = access.getHighlightColor(); + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + geometryUBO.highlightColor.set(colour.getRed(), colour.getGreen(), colour.getBlue(), 1.0f); + if (geometryUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + public CVKRenderUpdateTask TaskUpdateCamera() { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + UpdateVertexPushConstants(); + }; + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKIconsRenderable.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKIconsRenderable.java new file mode 100644 index 0000000000..a35a59e859 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKIconsRenderable.java @@ -0,0 +1,1855 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.renderables; + +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.*; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.*; +import static org.lwjgl.system.MemoryUtil.*; +import static org.lwjgl.vulkan.VK10.*; +import static org.lwjgl.system.MemoryStack.stackPush; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKIconTextureAtlas; +import au.gov.asd.tac.constellation.utilities.color.ConstellationColor; +import au.gov.asd.tac.constellation.utilities.graphics.Matrix44f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector4f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector4i; +import au.gov.asd.tac.constellation.utilities.visual.VisualAccess; +import au.gov.asd.tac.constellation.utilities.visual.VisualChange; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool.CVKDescriptorPoolRequirements; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import au.gov.asd.tac.constellation.visual.vulkan.CVKRenderUpdateTask; +import au.gov.asd.tac.constellation.visual.vulkan.CVKSwapChain; +import au.gov.asd.tac.constellation.visual.vulkan.CVKVisualProcessor; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKBuffer; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKCommandBuffer; +import java.nio.ByteBuffer; +import java.nio.LongBuffer; +import java.util.ArrayList; +import java.util.List; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.vulkan.VkBufferViewCreateInfo; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkCommandBufferInheritanceInfo; +import org.lwjgl.vulkan.VkDescriptorBufferInfo; +import org.lwjgl.vulkan.VkDescriptorImageInfo; +import org.lwjgl.vulkan.VkDescriptorSetAllocateInfo; +import org.lwjgl.vulkan.VkDescriptorSetLayoutBinding; +import org.lwjgl.vulkan.VkDescriptorSetLayoutCreateInfo; +import org.lwjgl.vulkan.VkPipelineLayoutCreateInfo; +import org.lwjgl.vulkan.VkPushConstantRange; +import org.lwjgl.vulkan.VkVertexInputAttributeDescription; +import org.lwjgl.vulkan.VkVertexInputBindingDescription; +import org.lwjgl.vulkan.VkWriteDescriptorSet; + + +/** + * What resources do icons have + * - vs: vertex buffer (icon indexes and bkg colour) + * - vs: xyzw buffer (icon positions) + * - vs: vertex ubo (camera vars) + * - gs: flag buffer (vertex flags) + * - gs: geometry ubo (proj mtx and render data) + * - fs: atlas texture sampler ( + * - fs: fragment ubo (hit test flag) + * + */ +public class CVKIconsRenderable extends CVKRenderable { + private static final int ICON_BITS = 16; + private static final int ICON_MASK = 0xffff; + public static final int SELECTED_BIT = 1; + public static final int DIMMED_BIT = 2; + + // Resource states. The atlas sampler handle is cached so we know the atlas + // state, ie if it doesn't match the one returned by the atlas, we know we + // need to recreate our descriptors to point to the new one. + private CVKRenderableResourceState positionBufferState = CVK_RESOURCE_CLEAN; + private CVKRenderableResourceState vertexFlagsBufferState = CVK_RESOURCE_CLEAN; + private long hIconAtlasSampler = VK_NULL_HANDLE; + private long hIconAtlasImageView = VK_NULL_HANDLE; + + // Resources recreated with the swap chain (dependent on the image count) + private LongBuffer pDescriptorSets = null; + private List hitTestPipelines = null; + private List displayCommandBuffers = null; + private List hittestCommandBuffers = null; + private List vertexBuffers = null; + private List vertexUniformBuffers = null; + private List geometryUniformBuffers = null; + + // The UBO staging buffers are a known size so created outside user events + private CVKBuffer cvkVertexUBStagingBuffer = null; + private CVKBuffer cvkGeometryUBStagingBuffer = null; + private final VertexUniformBufferObject vertexUBO = new VertexUniformBufferObject(); + private final GeometryUniformBufferObject geometryUBO = new GeometryUniformBufferObject(); + private final Matrix44f mtxHighlightColour = Matrix44f.identity(); + + // Resources recreated only through user events + private int vertexCount = 0; + private CVKBuffer cvkVertexStagingBuffer = null; + private CVKBuffer cvkPositionStagingBuffer = null; + private CVKBuffer cvkPositionBuffer = null; + private CVKBuffer cvkVertexFlagsStagingBuffer = null; + private CVKBuffer cvkVertexFlagsBuffer = null; + private long hPositionBufferView = VK_NULL_HANDLE; + private long hVertexFlagsBufferView = VK_NULL_HANDLE; + + // Push constants for shaders contains the MV matrix and drawHitTest int + private ByteBuffer vertexPushConstants = null; + private ByteBuffer hitTestPushConstants = null; + + + // ========================> Debuggering <======================== \\ + + private void SetPositionBufferState(final CVKRenderableResourceState state) { + CVKAssert(!(positionBufferState == CVK_RESOURCE_NEEDS_REBUILD && state == CVK_RESOURCE_NEEDS_UPDATE)); + if (LOGSTATECHANGE) { + GetLogger().info("%d\t positionBufferState %s -> %s\tSource: %s", + cvkVisualProcessor.GetFrameNumber(), positionBufferState.name(), state.name(), GetParentMethodName()); + } + positionBufferState = state; + } + + private void SetVertexFlagsBufferState(final CVKRenderableResourceState state) { + CVKAssert(!(vertexFlagsBufferState == CVK_RESOURCE_NEEDS_REBUILD && state == CVK_RESOURCE_NEEDS_UPDATE)); + if (LOGSTATECHANGE) { + GetLogger().info("%d\t vertexFlagsBufferState %s -> %s\tSource: %s", + cvkVisualProcessor.GetFrameNumber(), vertexFlagsBufferState.name(), state.name(), GetParentMethodName()); + } + vertexFlagsBufferState = state; + } + + + // ========================> Classes <======================== \\ + + private static class Vertex { + // This looks a little weird for Java, but LWJGL and JOGL both require + // contiguous memory which is passed to the native GL or VK libraries. + private static final int BYTES = Vector4f.BYTES + Vector4i.BYTES; + private static final int OFFSETOF_DATA = Vector4f.BYTES; + private static final int OFFSET_BKGCLR = 0; + private static final int BINDING = 0; + private Vector4f backgroundIconColour = new Vector4f(); + private Vector4i data = new Vector4i(); + + private Vertex() {} + + public Vertex(Vector4i inData, Vector4f inColour) { + data = inData; + backgroundIconColour = inColour; + } + + public void SetBackgroundIconColour(ConstellationColor colour) { + backgroundIconColour.a[0] = colour.getRed(); + backgroundIconColour.a[1] = colour.getGreen(); + backgroundIconColour.a[2] = colour.getBlue(); + } + + public void SetVertexVisibility(float visibility) { + backgroundIconColour.a[3] = visibility; + } + + public void SetIconData(int mainIconIndices, int decoratorWestIconIndices, int decoratorEastIconIndices, int vertexIndex) { + data.set(mainIconIndices, decoratorWestIconIndices, decoratorEastIconIndices, vertexIndex); + } + + public void CopyToSequentially(ByteBuffer buffer) { + buffer.putFloat(backgroundIconColour.a[0]); + buffer.putFloat(backgroundIconColour.a[1]); + buffer.putFloat(backgroundIconColour.a[2]); + buffer.putFloat(backgroundIconColour.a[3]); + buffer.putInt(data.a[0]); + buffer.putInt(data.a[1]); + buffer.putInt(data.a[2]); + buffer.putInt(data.a[3]); + } + + /** + * A VkVertexInputBindingDescription defines the rate at which data is + * consumed by vertex shader (per vertex or per instance). + * The input rate determines whether to move to the next data entry after + * each vertex or after each instance. + * The binding description also defines the vertex stride, the number of + * bytes that must be stepped from vertex n-1 to vertex n. + * + * @return Binding description for the FPS vertex type + */ + private static VkVertexInputBindingDescription.Buffer GetBindingDescription() { + + VkVertexInputBindingDescription.Buffer bindingDescription = + VkVertexInputBindingDescription.callocStack(1); + + // If we bind multiple vertex buffers with different descriptions + // this is the index of this description occupies in the array of + // bound descriptions. + bindingDescription.binding(BINDING); + bindingDescription.stride(Vertex.BYTES); + bindingDescription.inputRate(VK_VERTEX_INPUT_RATE_VERTEX); + + return bindingDescription; + } + + /** + * A VkVertexInputAttributeDescription describes each element int the + * vertex buffer. + * binding: matches the binding member of VkVertexInputBindingDescription + * location: corresponds to the layout(location = #) in the vertex shader + * for this element (0 for data, 1 for bkgClr). + * format: format the shader will interpret this as. + * offset: bytes from the start of the vertex this attribute starts at + * + * @return + */ + private static VkVertexInputAttributeDescription.Buffer GetAttributeDescriptions() { + + VkVertexInputAttributeDescription.Buffer attributeDescriptions = + VkVertexInputAttributeDescription.callocStack(2); + + // backgroundIconColor + VkVertexInputAttributeDescription posDescription = attributeDescriptions.get(0); + posDescription.binding(BINDING); + posDescription.location(0); + posDescription.format(VK_FORMAT_R32G32B32A32_SFLOAT); + posDescription.offset(OFFSET_BKGCLR); + + // data + VkVertexInputAttributeDescription colorDescription = attributeDescriptions.get(1); + colorDescription.binding(BINDING); + colorDescription.location(1); + colorDescription.format(VK_FORMAT_R32G32B32A32_SINT); + colorDescription.offset(OFFSETOF_DATA); + + return attributeDescriptions.rewind(); + } + } + + @Override + protected VkVertexInputBindingDescription.Buffer GetVertexBindingDescription() { + return Vertex.GetBindingDescription(); + } + + @Override + protected VkVertexInputAttributeDescription.Buffer GetVertexAttributeDescriptions() { + return Vertex.GetAttributeDescriptions(); + } + + private static class Position { + private static final int BYTES = 2 * Vector4f.BYTES; + public final Vector4f position1; + public final Vector4f position2; + + public Position(float x1, float y1, float z1, float radius1, + float x2, float y2, float z2, float radius2) { + position1 = new Vector4f(x1, y1, z1, radius1); + position2 = new Vector4f(x2, y2, z2, radius2); + } + + public void CopyToSequentially(ByteBuffer buffer) { + buffer.putFloat(position1.getX()); + buffer.putFloat(position1.getY()); + buffer.putFloat(position1.getZ()); + buffer.putFloat(position1.getW()); + buffer.putFloat(position2.getX()); + buffer.putFloat(position2.getY()); + buffer.putFloat(position2.getZ()); + buffer.putFloat(position2.getW()); + } + } + + private static class VertexUniformBufferObject { + public float morphMix = 0; + public float visibilityLow = 0; + public float visibilityHigh = 0; + private static Integer padding = null; + + private static int SizeOf() { + if (padding == null) { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + final int minAlignment = CVKDevice.GetMinUniformBufferAlignment(); + + // The matrices are 64 bytes each so should line up on a boundary (unless the minimum alignment is huge) + CVKAssert(minAlignment <= (16 * Float.BYTES)); + + int sizeof = 1 * Float.BYTES + // morphMix + 1 * Float.BYTES + // visibilityLow + 1 * Float.BYTES; // visibilityHigh + + final int overrun = sizeof % minAlignment; + padding = Integer.valueOf(overrun > 0 ? minAlignment - overrun : 0); + } + + return 1 * Float.BYTES + // morphMix + 1 * Float.BYTES + // visibilityLow + 1 * Float.BYTES + // visibilityHigh + padding; + } + + private void CopyTo(ByteBuffer buffer) { + buffer.putFloat(morphMix); + buffer.putFloat(visibilityLow); + buffer.putFloat(visibilityHigh); + + for (int i = 0; i < padding; ++i) { + buffer.put((byte)0); + } + } + } + + private static class GeometryUniformBufferObject { + private int iconsPerRowColumn; + private int iconsPerLayer; + private int atlas2DDimension; + private float pixelDensity = 0; + private final Matrix44f highlightColor = Matrix44f.identity(); + private final Matrix44f pMatrix = new Matrix44f(); + private static Integer padding = null; + + private static int SizeOf() { + if (padding == null) { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + final int minAlignment = CVKDevice.GetMinUniformBufferAlignment(); + + // The matrices are 64 bytes each so should line up on a boundary (unless the minimum alignment is huge) + CVKAssert(minAlignment <= (16 * Float.BYTES)); + + int sizeof = 1 * Integer.BYTES + // iconsPerRowColumn + 1 * Integer.BYTES + // iconsPerLayer + 1 * Integer.BYTES + // atlas2DDimension + 1 * Float.BYTES; // pixelDensity + + final int overrun = sizeof % minAlignment; + padding = Integer.valueOf(overrun > 0 ? minAlignment - overrun : 0); + } + + return 1 * Integer.BYTES + // iconsPerRowColumn + 1 * Integer.BYTES + // iconsPerLayer + 1 * Integer.BYTES + // atlas2DDimension + 1 * Float.BYTES + // pixelDensity + padding + + 16 * Float.BYTES + // highlightColor + 16 * Float.BYTES; // pMatrix + } + + private void CopyTo(ByteBuffer buffer) { + buffer.putInt(iconsPerRowColumn); + buffer.putInt(iconsPerLayer); + buffer.putInt(atlas2DDimension); + buffer.putFloat(pixelDensity); + + for (int i = 0; i < padding; ++i) { + buffer.put((byte)0); + } + + PutMatrix44f(buffer, highlightColor); + PutMatrix44f(buffer, pMatrix); + } + } + + + // ========================> Shaders <======================== \\ + + @Override + protected String GetVertexShaderName() { return "VertexIcon.vs"; } + + @Override + protected String GetGeometryShaderName() { return "VertexIcon.gs"; } + + @Override + protected String GetFragmentShaderName() { return "VertexIcon.fs"; } + + + // ========================> Lifetime <======================== \\ + + public CVKIconsRenderable(CVKVisualProcessor visualProcessor) { + super(visualProcessor); + } + + private void CreateUBOStagingBuffers() { + cvkVertexUBStagingBuffer = CVKBuffer.Create(VertexUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKIconsRenderable.CreateUBOStagingBuffers cvkVertexUBStagingBuffer"); + cvkGeometryUBStagingBuffer = CVKBuffer.Create(GeometryUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKIconsRenderable.CreateUBOStagingBuffers cvkGeometryUBStagingBuffer"); + } + + @Override + public int Initialise() { + int ret = super.Initialise(); + if (VkFailed(ret)) { return ret; } + + // Check for double initialisation + CVKAssert(hDescriptorLayout == VK_NULL_HANDLE); + + CreatePushConstants(); + + ret = CreateDescriptorLayout(); + if (VkFailed(ret)) { return ret; } + + ret = CreatePipelineLayout(); + if (VkFailed(ret)) { return ret; } + + CreateUBOStagingBuffers(); + + return ret; + } + + private void DestroyStagingBuffers() { + if (cvkVertexStagingBuffer != null) { + cvkVertexStagingBuffer.Destroy(); + cvkVertexStagingBuffer = null; + } + if (cvkPositionStagingBuffer != null) { + cvkPositionStagingBuffer.Destroy(); + cvkPositionStagingBuffer = null; + } + if (cvkVertexFlagsStagingBuffer != null) { + cvkVertexFlagsStagingBuffer.Destroy(); + cvkVertexFlagsStagingBuffer = null; + } + + if (cvkVertexUBStagingBuffer != null) { + cvkVertexUBStagingBuffer.Destroy(); + cvkVertexUBStagingBuffer = null; + } + if (cvkGeometryUBStagingBuffer != null) { + cvkGeometryUBStagingBuffer.Destroy(); + cvkGeometryUBStagingBuffer = null; + } + } + + @Override + public void Destroy() { + DestroyVertexBuffers(); + DestroyPositionBuffer(); + DestroyVertexFlagsBuffer(); + DestroyVertexUniformBuffers(); + DestroyGeometryUniformBuffers(); + DestroyDescriptorSets(); + DestroyDescriptorLayout(); + DestroyPipelines(); + DestroyPipelineLayout(); + DestroyCommandBuffers(); + DestroyStagingBuffers(); + DestroyPushConstants(); + + CVKAssertNull(vertexBuffers); + CVKAssertNull(cvkPositionBuffer); + CVKAssertNull(hPositionBufferView); + CVKAssertNull(cvkVertexFlagsBuffer); + CVKAssertNull(hVertexFlagsBufferView); + CVKAssertNull(vertexUniformBuffers); + CVKAssertNull(geometryUniformBuffers); + CVKAssertNull(pDescriptorSets); + CVKAssertNull(hDescriptorLayout); + CVKAssertNull(displayCommandBuffers); + CVKAssertNull(displayPipelines); + CVKAssertNull(hPipelineLayout); + CVKAssertNull(vertexPushConstants); + CVKAssertNull(hitTestPushConstants); + } + + + // ========================> Swap chain <======================== \\ + + @Override + protected int DestroySwapChainResources() { + this.cvkSwapChain = null; + + // We only need to recreate these resources if the number of images in + // the swapchain changes or if this is the first call after the initial + // swapchain is created. + if (displayPipelines != null && swapChainImageCountChanged) { + DestroyVertexBuffers(); + DestroyVertexUniformBuffers(); + DestroyGeometryUniformBuffers(); + DestroyDescriptorSets(); + DestroyCommandBuffers(); + DestroyPipelines(); + } + + return VK_SUCCESS; + } + + @Override + public int SetNewSwapChain(CVKSwapChain newSwapChain) { + int ret = super.SetNewSwapChain(newSwapChain); + if (VkFailed(ret)) { return ret; } + + if (swapChainImageCountChanged) { + // The number of images has changed, we need to rebuild all image + // buffered resources + SetVertexUniformBufferState(CVK_RESOURCE_NEEDS_REBUILD); + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_REBUILD); + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + SetCommandBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + SetDescriptorSetsState(CVK_RESOURCE_NEEDS_REBUILD); + SetPipelinesState(CVK_RESOURCE_NEEDS_REBUILD); + } else { + // View frustum and projection matrix likely have changed. We don't + // need to rebuild our displayPipelines as the frustum is set by dynamic + // state in RecordDisplayCommandBuffer + if (geometryUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + } + + return ret; + } + + + // ========================> Vertex buffers <======================== \\ + + private int CreateVertexBuffers() { + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + + // We can only create vertex buffers if we have something to put in them + if (cvkVertexStagingBuffer.GetBufferSize() > 0) { + int imageCount = cvkSwapChain.GetImageCount(); + vertexBuffers = new ArrayList<>(); + + for (int i = 0; i < imageCount; ++i) { + CVKBuffer cvkVertexBuffer = CVKBuffer.Create(cvkVertexStagingBuffer.GetBufferSize(), + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKIconsRenderable cvkVertexBuffer %d", i)); + vertexBuffers.add(cvkVertexBuffer); + } + + // Populate them with some values + return UpdateVertexBuffers(); + } + + return ret; + } + + private int UpdateVertexBuffers() { + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssert(cvkVertexStagingBuffer != null); + CVKAssert(vertexBuffers != null); + CVKAssert(vertexBuffers.size() > 0); + CVKAssert(cvkVertexStagingBuffer.GetBufferSize() == vertexBuffers.get(0).GetBufferSize()); + int ret = VK_SUCCESS; + +// List DEBUG_vertexDescriptors = new ArrayList<>(); +// DEBUG_vertexDescriptors.add(new DEBUG_CVKBufferElementDescriptor("r", Float.TYPE)); +// DEBUG_vertexDescriptors.add(new DEBUG_CVKBufferElementDescriptor("g", Float.TYPE)); +// DEBUG_vertexDescriptors.add(new DEBUG_CVKBufferElementDescriptor("b", Float.TYPE)); +// DEBUG_vertexDescriptors.add(new DEBUG_CVKBufferElementDescriptor("a", Float.TYPE)); +// DEBUG_vertexDescriptors.add(new DEBUG_CVKBufferElementDescriptor("mainIconIndices", Integer.TYPE)); +// DEBUG_vertexDescriptors.add(new DEBUG_CVKBufferElementDescriptor("decoratorWestIconIndices", Integer.TYPE)); +// DEBUG_vertexDescriptors.add(new DEBUG_CVKBufferElementDescriptor("decoratorEastIconIndices", Integer.TYPE)); +// DEBUG_vertexDescriptors.add(new DEBUG_CVKBufferElementDescriptor("vertexIndex", Integer.TYPE)); +// cvkVertexStagingBuffer.DEBUGPRINT(DEBUG_vertexDescriptors); + + for (int i = 0; i < vertexBuffers.size(); ++i) { + CVKBuffer cvkVertexBuffer = vertexBuffers.get(i); + ret = cvkVertexBuffer.CopyFrom(cvkVertexStagingBuffer); + if (VkFailed(ret)) { return ret; } + } + + // Note the staging buffer is not freed as we can simplify the update tasks + // by just updating it and then copying it over again during ProcessRenderTasks(). + SetVertexBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public int GetVertexCount() { return cvkVisualProcessor.GetDrawFlags().drawNodes() ? vertexCount : 0; } + + private void DestroyVertexBuffers() { + if (vertexBuffers != null) { + vertexBuffers.forEach(el -> {el.Destroy();}); + vertexBuffers.clear(); + vertexBuffers = null; + } + } + + + // ========================> Texel buffers <======================== \\ + + private int CreatePositionBuffer() { + CVKAssertNotNull(cvkSwapChain); + CVKAssert(cvkPositionBuffer == null); + cvkVisualProcessor.VerifyInRenderThread(); + int ret = VK_SUCCESS; + + cvkPositionBuffer = CVKBuffer.Create(cvkPositionStagingBuffer.GetBufferSize(), + VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + "CVKIconsRenderable.CreatePositionBuffer cvkPositionBuffer"); + CVKAssertNotNull(cvkPositionBuffer.GetBufferHandle()); + + try (MemoryStack stack = stackPush()) { + // NB: we have already checked VK_FORMAT_R32G32B32A32_SFLOAT can be used as a texel buffer + // format in CVKDevice. If the format is changed here we need to check for its support in + // CVKDevice. + VkBufferViewCreateInfo vkViewInfo = VkBufferViewCreateInfo.callocStack(stack); + vkViewInfo.sType(VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO); + vkViewInfo.buffer(cvkPositionBuffer.GetBufferHandle()); + vkViewInfo.format(VK_FORMAT_R32G32B32A32_SFLOAT); + vkViewInfo.offset(0); + vkViewInfo.range(VK_WHOLE_SIZE); + + LongBuffer pBufferView = stack.mallocLong(1); + ret = vkCreateBufferView(CVKDevice.GetVkDevice(), vkViewInfo, null, pBufferView); + if (VkFailed(ret)) { return ret; } + hPositionBufferView = pBufferView.get(0); + GetLogger().info("Created CVKIconsRenderable.hPositionBufferView: 0x%016X", hPositionBufferView); + + // Descriptor sets will reference the old buffer view until updated + SetDescriptorSetsState(pDescriptorSets != null ? CVK_RESOURCE_NEEDS_UPDATE : CVK_RESOURCE_NEEDS_REBUILD); + } + + return UpdatePositionBuffer(); + } + + private int UpdatePositionBuffer() { +// List DEBUG_positionDescriptors = new ArrayList<>(); +// DEBUG_positionDescriptors.add(new DEBUG_CVKBufferElementDescriptor("X1", Float.TYPE)); +// DEBUG_positionDescriptors.add(new DEBUG_CVKBufferElementDescriptor("Y1", Float.TYPE)); +// DEBUG_positionDescriptors.add(new DEBUG_CVKBufferElementDescriptor("Z1", Float.TYPE)); +// DEBUG_positionDescriptors.add(new DEBUG_CVKBufferElementDescriptor("Rad1", Float.TYPE)); +// DEBUG_positionDescriptors.add(new DEBUG_CVKBufferElementDescriptor("X2", Float.TYPE)); +// DEBUG_positionDescriptors.add(new DEBUG_CVKBufferElementDescriptor("Y2", Float.TYPE)); +// DEBUG_positionDescriptors.add(new DEBUG_CVKBufferElementDescriptor("Z2", Float.TYPE)); +// DEBUG_positionDescriptors.add(new DEBUG_CVKBufferElementDescriptor("Rad2", Float.TYPE)); +// cvkPositionStagingBuffer.DEBUGPRINT(DEBUG_positionDescriptors); + + int ret = cvkPositionBuffer.CopyFrom(cvkPositionStagingBuffer); + if (VkFailed(ret)) { return ret; } + + SetPositionBufferState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private void DestroyPositionBuffer() { + if (cvkPositionBuffer != null) { + cvkPositionBuffer.Destroy(); + cvkPositionBuffer = null; + } + if (hPositionBufferView != VK_NULL_HANDLE) { + GetLogger().info("Destroying CVKIconsRenderable.hPositionBufferView: 0x%016X", hPositionBufferView); + vkDestroyBufferView(CVKDevice.GetVkDevice(), hPositionBufferView, null); + hPositionBufferView = VK_NULL_HANDLE; + } + } + + private int CreateVertexFlagsBuffer() { + CVKAssertNotNull(cvkSwapChain); + CVKAssert(cvkVertexFlagsBuffer == null); + cvkVisualProcessor.VerifyInRenderThread(); + int ret = VK_SUCCESS; + + cvkVertexFlagsBuffer = CVKBuffer.Create(cvkVertexFlagsStagingBuffer.GetBufferSize(), + VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + "CVKIconsRenderable.CreateVertexFlagsBuffer cvkVertexFlagsBuffer"); + + try (MemoryStack stack = stackPush()) { + // NB: we have already checked VK_FORMAT_R8_SINT can be used as a texel buffer + // format in CVKDevice. If the format is changed here we need to check for its support in + // CVKDevice. + VkBufferViewCreateInfo vkViewInfo = VkBufferViewCreateInfo.callocStack(stack); + vkViewInfo.sType(VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO); + vkViewInfo.buffer(cvkVertexFlagsBuffer.GetBufferHandle()); + vkViewInfo.format(VK_FORMAT_R8_SINT); + vkViewInfo.offset(0); + vkViewInfo.range(VK_WHOLE_SIZE); + + LongBuffer pBufferView = stack.mallocLong(1); + ret = vkCreateBufferView(CVKDevice.GetVkDevice(), vkViewInfo, null, pBufferView); + if (VkFailed(ret)) { return ret; } + hVertexFlagsBufferView = pBufferView.get(0); + GetLogger().info("Created CVKIconsRenderable.hVertexFlagsBufferView: 0x%016X", hVertexFlagsBufferView); + + // Descriptor sets will reference the old buffer view until updated + SetDescriptorSetsState(pDescriptorSets != null ? CVK_RESOURCE_NEEDS_UPDATE : CVK_RESOURCE_NEEDS_REBUILD); + } + + return UpdateVertexFlagsBuffer(); + } + + private int UpdateVertexFlagsBuffer() { + int ret = cvkVertexFlagsBuffer.CopyFrom(cvkVertexFlagsStagingBuffer); + if (VkFailed(ret)) { return ret; } + SetVertexFlagsBufferState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private void DestroyVertexFlagsBuffer() { + if (cvkVertexFlagsBuffer != null) { + cvkVertexFlagsBuffer.Destroy(); + cvkVertexFlagsBuffer = null; + } + if (hVertexFlagsBufferView != VK_NULL_HANDLE) { + GetLogger().info("Destroying CVKIconsRenderable.hVertexFlagsBufferView: 0x%016X", hVertexFlagsBufferView); + vkDestroyBufferView(CVKDevice.GetVkDevice(), hVertexFlagsBufferView, null); + hVertexFlagsBufferView = VK_NULL_HANDLE; + } + } + + + // ========================> Uniform buffers <======================== \\ + + // TODO: make sure these are called when the camera changes etc + + private int CreateVertexUniformBuffers() { + CVKAssertNotNull(cvkSwapChain); + CVKAssert(vertexUniformBuffers == null); + + vertexUniformBuffers = new ArrayList<>(); + for (int i = 0; i < cvkSwapChain.GetImageCount(); ++i) { + CVKBuffer vertexUniformBuffer = CVKBuffer.Create(VertexUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKIconsRenderable vertexUniformBuffer %d", i)); + vertexUniformBuffers.add(vertexUniformBuffer); + } + return UpdateVertexUniformBuffers(); + } + + private int UpdateVertexUniformBuffers() { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkVertexUBStagingBuffer); + CVKAssertNotNull(vertexUniformBuffers); + CVKAssertNotNull(cvkVisualProcessor); + CVKAssertNotNull(cvkVisualProcessor.getDisplayCamera()); + CVKAssertNotNull(vertexUBO); + CVKAssert(vertexUniformBuffers.size() > 0); + + int ret = VK_SUCCESS; + + // Populate the UBO. This is easy to deal with, but not super efficient + // as we are effectively staging into the staging buffer below. + vertexUBO.morphMix = cvkVisualProcessor.getDisplayCamera().getMix(); + + // TODO: replace with constants. In the JOGL version these were in a static var CAMERA that never changed + vertexUBO.visibilityLow = cvkVisualProcessor.getDisplayCamera().getVisibilityLow(); + vertexUBO.visibilityHigh = cvkVisualProcessor.getDisplayCamera().getVisibilityHigh(); + + // Staging buffer so our VBO can be device local (most performant memory) + ByteBuffer pMemory = cvkVertexUBStagingBuffer.StartMemoryMap(0, VertexUniformBufferObject.SizeOf()); + { + vertexUBO.CopyTo(pMemory); + } + cvkVertexUBStagingBuffer.EndMemoryMap(); + pMemory = null; + + // Copy the staging buffer into the uniform buff-er on the device + final int imageCount = cvkSwapChain.GetImageCount(); + for (int i = 0; i < imageCount; ++i) { + ret = vertexUniformBuffers.get(i).CopyFrom(cvkVertexUBStagingBuffer); + if (VkFailed(ret)) { return ret; } + } + + UpdateVertexPushConstants(); + + // We are done, reset the resource state + SetVertexUniformBufferState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private void DestroyVertexUniformBuffers() { + if (vertexUniformBuffers != null) { + vertexUniformBuffers.forEach(el -> {el.Destroy();}); + vertexUniformBuffers = null; + } + } + + private int CreateGeometryUniformBuffers() { + CVKAssertNotNull(cvkSwapChain); + CVKAssert(geometryUniformBuffers == null); + + geometryUniformBuffers = new ArrayList<>(); + for (int i = 0; i < cvkSwapChain.GetImageCount(); ++i) { + CVKBuffer geometryUniformBuffer = CVKBuffer.Create(GeometryUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKIconsRenderable geometryUniformBuffer %d", i)); + geometryUniformBuffers.add(geometryUniformBuffer); + } + return UpdateGeometryUniformBuffers(); + } + + private int UpdateGeometryUniformBuffers() { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkGeometryUBStagingBuffer); + CVKAssertNotNull(geometryUniformBuffers); + CVKAssert(geometryUniformBuffers.size() > 0); + + int ret = VK_SUCCESS; + + // Populate the UBO. This is easy to deal with, but not super efficient + // as we are effectively staging into the staging buffer below. + geometryUBO.pMatrix.set(cvkVisualProcessor.GetProjectionMatrix()); + geometryUBO.pixelDensity = cvkVisualProcessor.GetPixelDensity(); + geometryUBO.highlightColor.set(mtxHighlightColour); + geometryUBO.iconsPerRowColumn = CVKIconTextureAtlas.GetInstance().serializableData.iconsPerRowColumn; + geometryUBO.iconsPerLayer = CVKIconTextureAtlas.GetInstance().serializableData.iconsPerLayer; + geometryUBO.atlas2DDimension = CVKIconTextureAtlas.GetInstance().serializableData.texture2DDimension; + + // Staging buffer so our VBO can be device local (most performant memory) + ByteBuffer pMemory = cvkGeometryUBStagingBuffer.StartMemoryMap(0, GeometryUniformBufferObject.SizeOf()); + { + geometryUBO.CopyTo(pMemory); + } + cvkGeometryUBStagingBuffer.EndMemoryMap(); + pMemory = null; + + // Copy the staging buffer into the uniform buffer on the device + final int imageCount = cvkSwapChain.GetImageCount(); + for (int i = 0; i < imageCount; ++i) { + ret = geometryUniformBuffers.get(i).CopyFrom(cvkGeometryUBStagingBuffer); + if (VkFailed(ret)) { return ret; } + } + + // We are done, reset the resource state + SetGeometryUniformBufferState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private void DestroyGeometryUniformBuffers() { + if (geometryUniformBuffers != null) { + geometryUniformBuffers.forEach(el -> {el.Destroy();}); + geometryUniformBuffers = null; + } + } + + // ========================> Push constants <======================== \\ + + private int CreatePushConstants() { + // Initialise push constants to identity mtx + vertexPushConstants = memAlloc(Matrix44f.BYTES); + PutMatrix44f(vertexPushConstants, IDENTITY_44F); + + // Set DrawHitTest to false + hitTestPushConstants = memAlloc(4); + hitTestPushConstants.putInt(0); + + vertexPushConstants.flip(); + hitTestPushConstants.flip(); + + return VK_SUCCESS; + } + + private void UpdateVertexPushConstants(){ + CVKAssertNotNull(cvkSwapChain); + + vertexPushConstants.clear(); + + // Update MV Matrix + PutMatrix44f(vertexPushConstants, cvkVisualProcessor.getDisplayModelViewMatrix()); + vertexPushConstants.flip(); + } + + private void UpdatePushConstantsHitTest(boolean drawHitTest){ + CVKAssertNotNull(cvkSwapChain); + + hitTestPushConstants.clear(); + + if (drawHitTest) { + hitTestPushConstants.putInt(1); + } else { + hitTestPushConstants.putInt(0); + } + + hitTestPushConstants.flip(); + } + + private void DestroyPushConstants() { + if (vertexPushConstants != null) { + memFree(vertexPushConstants); + vertexPushConstants = null; + } + + if (hitTestPushConstants != null) { + memFree(hitTestPushConstants); + hitTestPushConstants = null; + } + } + + + // ========================> Command buffers <======================== \\ + + public int CreateCommandBuffers(){ + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + int imageCount = cvkSwapChain.GetImageCount(); + + displayCommandBuffers = new ArrayList<>(imageCount); + hittestCommandBuffers = new ArrayList<>(imageCount); + + for (int i = 0; i < imageCount; ++i) { + CVKCommandBuffer buffer = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_SECONDARY, GetLogger(), String.format("CVKIconsRenderable %d", i)); + displayCommandBuffers.add(buffer); + + CVKCommandBuffer offscreenBuffer = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_SECONDARY, GetLogger(), String.format("CVKIconsRenderable Offscreen Buffer %d", i)); + hittestCommandBuffers.add(offscreenBuffer); + } + + SetCommandBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public VkCommandBuffer GetDisplayCommandBuffer(int imageIndex) { + return displayCommandBuffers.get(imageIndex).GetVKCommandBuffer(); + } + + @Override + public VkCommandBuffer GetHitTestCommandBuffer(int imageIndex) { + return hittestCommandBuffers.get(imageIndex).GetVKCommandBuffer(); + } + + @Override + public int RecordDisplayCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int imageIndex){ + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(CVKDevice.GetCommandPoolHandle()); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + CVKCommandBuffer commandBuffer = displayCommandBuffers.get(imageIndex); + CVKAssert(commandBuffer != null); + CVKAssert(displayPipelines.get(imageIndex) != null); + + commandBuffer.BeginRecordSecondary(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, inheritanceInfo); + + commandBuffer.SetViewPort(cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight()); + commandBuffer.SetScissor(cvkVisualProcessor.GetCanvas().GetCurrentSurfaceExtent()); + + commandBuffer.BindGraphicsPipeline(displayPipelines.get(imageIndex)); + commandBuffer.BindVertexInput(vertexBuffers.get(imageIndex).GetBufferHandle()); + + // Push MV matrix to the shader + commandBuffer.PushConstants(hPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, vertexPushConstants); + + // Push drawHitTest flag to the shaders + commandBuffer.PushConstants(hPipelineLayout, VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, Matrix44f.BYTES, hitTestPushConstants); + + commandBuffer.BindGraphicsDescriptorSets(hPipelineLayout, pDescriptorSets.get(imageIndex)); + + commandBuffer.Draw(GetVertexCount()); + + ret = commandBuffer.FinishRecord(); + if (VkFailed(ret)) { return ret; } + + return ret; + } + + @Override + public int RecordHitTestCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int imageIndex){ + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssert(CVKDevice.GetCommandPoolHandle() != VK_NULL_HANDLE); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + // Set the hit test flag in the shaders to true + UpdatePushConstantsHitTest(true); + + CVKCommandBuffer commandBuffer = hittestCommandBuffers.get(imageIndex); + CVKAssertNotNull(commandBuffer); + CVKAssertNotNull(hitTestPipelines.get(imageIndex)); + + ret = commandBuffer.BeginRecordSecondary(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, + inheritanceInfo); + if (VkFailed(ret)) { return ret; } + + commandBuffer.SetViewPort(cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight()); + commandBuffer.SetScissor(cvkVisualProcessor.GetCanvas().GetCurrentSurfaceExtent()); + + commandBuffer.BindGraphicsPipeline(hitTestPipelines.get(imageIndex)); + commandBuffer.BindVertexInput(vertexBuffers.get(imageIndex).GetBufferHandle()); + + // Push MV matrix to the shader + commandBuffer.PushConstants(hPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, vertexPushConstants); + + // Push drawHitTest flag to the shaders + commandBuffer.PushConstants(hPipelineLayout, VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, Matrix44f.BYTES, hitTestPushConstants); + + commandBuffer.BindGraphicsDescriptorSets(hPipelineLayout, pDescriptorSets.get(imageIndex)); + + commandBuffer.Draw(GetVertexCount()); + + ret = commandBuffer.FinishRecord(); + if (VkFailed(ret)) { return ret; } + + // Reset hit test flag to false + UpdatePushConstantsHitTest(false); + + return ret; + } + + private void DestroyCommandBuffers() { + if (null != displayCommandBuffers) { + displayCommandBuffers.forEach(el -> {el.Destroy();}); + displayCommandBuffers.clear(); + displayCommandBuffers = null; + } + + if (null != hittestCommandBuffers) { + hittestCommandBuffers.forEach(el -> {el.Destroy();}); + hittestCommandBuffers.clear(); + hittestCommandBuffers = null; + } + } + + + // ========================> Descriptors <======================== \\ + + private int CreateDescriptorLayout() { + int ret; + + try (MemoryStack stack = stackPush()) { + VkDescriptorSetLayoutBinding.Buffer bindings = VkDescriptorSetLayoutBinding.callocStack(6, stack); + + // 0: Vertex uniform buffer + VkDescriptorSetLayoutBinding vertexUBDSLB = bindings.get(0); + vertexUBDSLB.binding(0); + vertexUBDSLB.descriptorCount(1); + vertexUBDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + vertexUBDSLB.pImmutableSamplers(null); + vertexUBDSLB.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + + // 1: Vertex samplerBuffer (position buffer) + VkDescriptorSetLayoutBinding vertexSamplerDSLB = bindings.get(1); + vertexSamplerDSLB.binding(1); + vertexSamplerDSLB.descriptorCount(1); + vertexSamplerDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + vertexSamplerDSLB.pImmutableSamplers(null); + vertexSamplerDSLB.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + + // 2: Geometry uniform buffer + VkDescriptorSetLayoutBinding geometryUBDSLB = bindings.get(2); + geometryUBDSLB.binding(2); + geometryUBDSLB.descriptorCount(1); + geometryUBDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + geometryUBDSLB.pImmutableSamplers(null); + geometryUBDSLB.stageFlags(VK_SHADER_STAGE_GEOMETRY_BIT); + + // 3: Geometry isamplerBuffer (vertex flags buffer) + VkDescriptorSetLayoutBinding geometrySamplerDSLB = bindings.get(3); + geometrySamplerDSLB.binding(3); + geometrySamplerDSLB.descriptorCount(1); + geometrySamplerDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + geometrySamplerDSLB.pImmutableSamplers(null); + geometrySamplerDSLB.stageFlags(VK_SHADER_STAGE_GEOMETRY_BIT); + + // 4: Fragment sampler2Darray (atlas) + VkDescriptorSetLayoutBinding fragmentSamplerDSLB = bindings.get(4); + fragmentSamplerDSLB.binding(4); + fragmentSamplerDSLB.descriptorCount(1); + fragmentSamplerDSLB.descriptorType(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + fragmentSamplerDSLB.pImmutableSamplers(null); + fragmentSamplerDSLB.stageFlags(VK_SHADER_STAGE_FRAGMENT_BIT); + + // 5: Fragment uniform buffer + VkDescriptorSetLayoutBinding fragmentUBDSLB = bindings.get(5); + fragmentUBDSLB.binding(5); + fragmentUBDSLB.descriptorCount(1); + fragmentUBDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + fragmentUBDSLB.pImmutableSamplers(null); + fragmentUBDSLB.stageFlags(VK_SHADER_STAGE_FRAGMENT_BIT); + + VkDescriptorSetLayoutCreateInfo layoutInfo = VkDescriptorSetLayoutCreateInfo.callocStack(stack); + layoutInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO); + layoutInfo.pBindings(bindings); + + LongBuffer pDescriptorSetLayout = stack.mallocLong(1); + + ret = vkCreateDescriptorSetLayout(CVKDevice.GetVkDevice(), layoutInfo, null, pDescriptorSetLayout); + if (VkSucceeded(ret)) { + hDescriptorLayout = pDescriptorSetLayout.get(0); + GetLogger().info("CVKIconsRenderable created hDescriptorLayout: 0x%016X", hDescriptorLayout); + } + } + return ret; + } + + private void DestroyDescriptorLayout() { + GetLogger().info("CVKIconsRenderable destroying hDescriptorLayout: 0x%016X", hDescriptorLayout); + vkDestroyDescriptorSetLayout(CVKDevice.GetVkDevice(), hDescriptorLayout, null); + hDescriptorLayout = VK_NULL_HANDLE; + } + + private int CreateDescriptorSets(MemoryStack stack) { + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + // The same layout is used for each descriptor set (each descriptor set is + // identical but allow the GPU and CPU to desynchronise. + final int imageCount = cvkSwapChain.GetImageCount(); + LongBuffer layouts = stack.mallocLong(imageCount); + for (int i = 0; i < imageCount; ++i) { + layouts.put(i, hDescriptorLayout); + } + + VkDescriptorSetAllocateInfo allocInfo = VkDescriptorSetAllocateInfo.callocStack(stack); + allocInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO); + allocInfo.descriptorPool(cvkDescriptorPool.GetDescriptorPoolHandle()); + allocInfo.pSetLayouts(layouts); + + // Allocate the descriptor sets from the descriptor pool, they'll be unitialised + pDescriptorSets = MemoryUtil.memAllocLong(imageCount); + ret = vkAllocateDescriptorSets(CVKDevice.GetVkDevice(), allocInfo, pDescriptorSets); + if (VkFailed(ret)) { return ret; } + + for (int i = 0; i < pDescriptorSets.capacity(); ++i) { + GetLogger().info("CVKIconsRenderable allocated hDescriptorSet %d: 0x%016X", i, pDescriptorSets.get(i)); + } + + return UpdateDescriptorSets(stack); + } + + // TODO: do we gain anything by having buffered UBOs? + private int UpdateDescriptorSets(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(pDescriptorSets); + CVKAssert(pDescriptorSets.capacity() > 0); + CVKAssert(hPositionBufferView != VK_NULL_HANDLE); + CVKAssert(hVertexFlagsBufferView != VK_NULL_HANDLE); + CVKAssertNotNull(vertexUniformBuffers); + CVKAssert(vertexUniformBuffers.size() > 0); + CVKAssertNotNull(geometryUniformBuffers); + CVKAssert(geometryUniformBuffers.size() > 0); + + int ret = VK_SUCCESS; + + final int imageCount = cvkSwapChain.GetImageCount(); + + // - Descriptor info structs - + // We create these to describe the different resources we want to address + // in shaders. We have one info struct per resource. We then create a + // write descriptor set structure for each resource for each image. For + // buffered resources like the the uniform buffers we wait to set the + // buffer resource until the image loop below. + + // Struct for the uniform buffer used by VertexIcon.vs + VkDescriptorBufferInfo.Buffer vertexUniformBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + // vertexUniformBufferInfo.buffer is set per imageIndex + vertexUniformBufferInfo.offset(0); + vertexUniformBufferInfo.range(VertexUniformBufferObject.SizeOf()); + + // Struct for texel buffer (positions) used by VertexIcon.vs + VkDescriptorBufferInfo.Buffer positionsTexelBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + positionsTexelBufferInfo.buffer(cvkPositionBuffer.GetBufferHandle()); + positionsTexelBufferInfo.offset(0); + positionsTexelBufferInfo.range(cvkPositionBuffer.GetBufferSize()); + + // Struct for the uniform buffer used by VertexIcon.gs + VkDescriptorBufferInfo.Buffer geometryUniformBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + // geometryBufferInfo.buffer is set per imageIndex + geometryUniformBufferInfo.offset(0); + geometryUniformBufferInfo.range(GeometryUniformBufferObject.SizeOf()); + + // Struct for texel buffer (vertex flags) used by VertexIcon.gs + VkDescriptorBufferInfo.Buffer vertexFlagsTexelBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + vertexFlagsTexelBufferInfo.buffer(cvkVertexFlagsBuffer.GetBufferHandle()); + vertexFlagsTexelBufferInfo.offset(0); + vertexFlagsTexelBufferInfo.range(cvkVertexFlagsBuffer.GetBufferSize()); + + // Struct for the size of the image sampler (atlas) used by VertexIcon.fs + VkDescriptorImageInfo.Buffer imageInfo = VkDescriptorImageInfo.callocStack(1, stack); + imageInfo.imageLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + imageInfo.imageView(CVKIconTextureAtlas.GetInstance().GetAtlasImageViewHandle()); + imageInfo.sampler(CVKIconTextureAtlas.GetInstance().GetAtlasSamplerHandle()); + + // We need 5 write descriptors, 2 for uniform buffers, 2 for texel buffers and 1 for texture sampler + VkWriteDescriptorSet.Buffer descriptorWrites = VkWriteDescriptorSet.callocStack(5, stack); + + // Vertex uniform buffer + VkWriteDescriptorSet vertexUBDescriptorWrite = descriptorWrites.get(0); + vertexUBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + vertexUBDescriptorWrite.dstBinding(0); + vertexUBDescriptorWrite.dstArrayElement(0); + vertexUBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + vertexUBDescriptorWrite.descriptorCount(1); + vertexUBDescriptorWrite.pBufferInfo(vertexUniformBufferInfo); + + // Vertex texel buffer (positions) + VkWriteDescriptorSet positionsTBDescriptorWrite = descriptorWrites.get(1); + positionsTBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + positionsTBDescriptorWrite.dstBinding(1); + positionsTBDescriptorWrite.dstArrayElement(0); + positionsTBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + positionsTBDescriptorWrite.descriptorCount(1); + positionsTBDescriptorWrite.pBufferInfo(positionsTexelBufferInfo); + positionsTBDescriptorWrite.pTexelBufferView(stack.longs(hPositionBufferView)); + + // Geometry uniform buffer + VkWriteDescriptorSet geometryUBDescriptorWrite = descriptorWrites.get(2); + geometryUBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + geometryUBDescriptorWrite.dstBinding(2); + geometryUBDescriptorWrite.dstArrayElement(0); + geometryUBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + geometryUBDescriptorWrite.descriptorCount(1); + geometryUBDescriptorWrite.pBufferInfo(geometryUniformBufferInfo); + + // Geometry texel buffer (vertex flags) + VkWriteDescriptorSet vertexFlagsTBDescriptorWrite = descriptorWrites.get(3); + vertexFlagsTBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + vertexFlagsTBDescriptorWrite.dstBinding(3); + vertexFlagsTBDescriptorWrite.dstArrayElement(0); + vertexFlagsTBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + vertexFlagsTBDescriptorWrite.descriptorCount(1); + vertexFlagsTBDescriptorWrite.pBufferInfo(vertexFlagsTexelBufferInfo); + vertexFlagsTBDescriptorWrite.pTexelBufferView(stack.longs(hVertexFlagsBufferView)); + + // Fragment image (atlas) sampler + VkWriteDescriptorSet atlasSamplerDescriptorWrite = descriptorWrites.get(4); + atlasSamplerDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + atlasSamplerDescriptorWrite.dstBinding(4); + atlasSamplerDescriptorWrite.dstArrayElement(0); + atlasSamplerDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + atlasSamplerDescriptorWrite.descriptorCount(1); + atlasSamplerDescriptorWrite.pImageInfo(imageInfo); + + for (int i = 0; i < imageCount; ++i) { + // Update the buffered resource buffers + vertexUniformBufferInfo.buffer(vertexUniformBuffers.get(i).GetBufferHandle()); + geometryUniformBufferInfo.buffer(geometryUniformBuffers.get(i).GetBufferHandle()); + + // Set the descriptor set we're updating in each write struct + long descriptorSet = pDescriptorSets.get(i); + descriptorWrites.forEach(el -> {el.dstSet(descriptorSet);}); + + // Update the descriptors with a write and no copy + GetLogger().info("CVKIconsRenderable updating descriptorSet: 0x%016X", descriptorSet); + vkUpdateDescriptorSets(CVKDevice.GetVkDevice(), descriptorWrites, null); + } + + // Cache atlas handles so we know when to recreate descriptors + hIconAtlasSampler = CVKIconTextureAtlas.GetInstance().GetAtlasSamplerHandle(); + hIconAtlasImageView = CVKIconTextureAtlas.GetInstance().GetAtlasImageViewHandle(); + + SetDescriptorSetsState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private int DestroyDescriptorSets() { + int ret = VK_SUCCESS; + + if (pDescriptorSets != null) { + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(cvkDescriptorPool.GetDescriptorPoolHandle()); + GetLogger().fine("CVKIconsRenderable returning %d descriptor sets to the pool", pDescriptorSets.capacity()); + + for (int i = 0; i < pDescriptorSets.capacity(); ++i) { + GetLogger().info("CVKIconsRenderable freeing hDescriptorSet %d: 0x%016X", i, pDescriptorSets.get(i)); + } + + // After calling vkFreeDescriptorSets, all descriptor sets in pDescriptorSets are invalid. + ret = vkFreeDescriptorSets(CVKDevice.GetVkDevice(), cvkDescriptorPool.GetDescriptorPoolHandle(), pDescriptorSets); + pDescriptorSets = null; + checkVKret(ret); + } + + return ret; + } + + @Override + public int DestroyDescriptorPoolResources() { + int ret = VK_SUCCESS; + + if (cvkDescriptorPool != null) { + return DestroyDescriptorSets(); + } + + return ret; + } + + @Override + public void IncrementDescriptorTypeRequirements(CVKDescriptorPoolRequirements reqs, CVKDescriptorPoolRequirements perImageReqs) { + // VertexIcon.vs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]; + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER]; + + // VertexIcon.gs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]; + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER]; + + // VertexIcon.fs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER]; + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]; + + // One set per image + ++perImageReqs.poolDesciptorSetCount; + } + + @Override + public int SetNewDescriptorPool(CVKDescriptorPool newDescriptorPool) { + int ret = super.SetNewDescriptorPool(newDescriptorPool); + if (VkFailed(ret)) { return ret; } + SetDescriptorSetsState(CVK_RESOURCE_NEEDS_REBUILD); + return ret; + } + + + // ========================> Pipelines <======================== \\ + + private int CreatePipelineLayout() { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(hDescriptorLayout); + + int ret; + try (MemoryStack stack = stackPush()) { + VkPushConstantRange.Buffer pushConstantRange; + pushConstantRange = VkPushConstantRange.calloc(2); + pushConstantRange.get(0).stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + pushConstantRange.get(0).size(64); + pushConstantRange.get(0).offset(0); + + pushConstantRange.get(1).stageFlags(VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_FRAGMENT_BIT); + pushConstantRange.get(1).size(4); + pushConstantRange.get(1).offset(64); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = VkPipelineLayoutCreateInfo.callocStack(stack); + pipelineLayoutInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO); + pipelineLayoutInfo.pSetLayouts(stack.longs(hDescriptorLayout)); + pipelineLayoutInfo.pPushConstantRanges(pushConstantRange); + LongBuffer pPipelineLayout = stack.longs(VK_NULL_HANDLE); + ret = vkCreatePipelineLayout(CVKDevice.GetVkDevice(), pipelineLayoutInfo, null, pPipelineLayout); + if (VkFailed(ret)) { return ret; } + hPipelineLayout = pPipelineLayout.get(0); + CVKAssert(hPipelineLayout != VK_NULL_HANDLE); + } + return ret; + } + + private void DestroyPipelineLayout() { + if (hPipelineLayout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(CVKDevice.GetVkDevice(), hPipelineLayout, null); + hPipelineLayout = VK_NULL_HANDLE; + } + } + + + // ========================> Display <======================== \\ + + @Override + public boolean NeedsDisplayUpdate() { + return vertexCount > 0 && + (positionBufferState != CVK_RESOURCE_CLEAN || + vertexFlagsBufferState != CVK_RESOURCE_CLEAN || + vertexUniformBufferState != CVK_RESOURCE_CLEAN || + geometryUniformBufferState != CVK_RESOURCE_CLEAN || + vertexBuffersState != CVK_RESOURCE_CLEAN || + commandBuffersState != CVK_RESOURCE_CLEAN || + descriptorSetsState != CVK_RESOURCE_CLEAN || + pipelinesState != CVK_RESOURCE_CLEAN || + hIconAtlasSampler != CVKIconTextureAtlas.GetInstance().GetAtlasSamplerHandle() || + hIconAtlasImageView != CVKIconTextureAtlas.GetInstance().GetAtlasImageViewHandle() ); + } + + @Override + public int DisplayUpdate() { + int ret = VK_SUCCESS; + cvkVisualProcessor.VerifyInRenderThread(); + + if (hIconAtlasSampler != CVKIconTextureAtlas.GetInstance().GetAtlasSamplerHandle() || + hIconAtlasImageView != CVKIconTextureAtlas.GetInstance().GetAtlasImageViewHandle()) { + if (descriptorSetsState != CVK_RESOURCE_NEEDS_REBUILD) { + descriptorSetsState = CVK_RESOURCE_NEEDS_UPDATE; + } + } + + try (MemoryStack stack = stackPush()) { + // Update vertex buffers + if (vertexBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + DestroyVertexBuffers(); + ret = CreateVertexBuffers(); + if (VkFailed(ret)) { return ret; } + } else if (vertexBuffersState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateVertexBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Update position buffer + if (positionBufferState == CVK_RESOURCE_NEEDS_REBUILD) { + DestroyPositionBuffer(); + ret = CreatePositionBuffer(); + if (VkFailed(ret)) { return ret; } + } else if (positionBufferState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdatePositionBuffer(); + if (VkFailed(ret)) { return ret; } + } + + // Update vertex flags buffer + if (vertexFlagsBufferState == CVK_RESOURCE_NEEDS_REBUILD) { + DestroyVertexFlagsBuffer(); + ret = CreateVertexFlagsBuffer(); + if (VkFailed(ret)) { return ret; } + } else if (vertexFlagsBufferState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateVertexFlagsBuffer(); + if (VkFailed(ret)) { return ret; } + } + + // Vertex uniform buffer (camera guff) + if (vertexUniformBufferState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateVertexUniformBuffers(); + if (VkFailed(ret)) { return ret; } + } else if (vertexUniformBufferState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateVertexUniformBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Geometry uniform buffer (projection, highlight colour and hit test) + if (geometryUniformBufferState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateGeometryUniformBuffers(); + if (VkFailed(ret)) { return ret; } + } else if (geometryUniformBufferState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateGeometryUniformBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Descriptors (binding values to shaders parameters) + if (descriptorSetsState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateDescriptorSets(stack); + if (VkFailed(ret)) { return ret; } + } else if (descriptorSetsState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateDescriptorSets(stack); + if (VkFailed(ret)) { return ret; } + } + + // Command buffers (rendering commands enqueued on the GPU) + if (commandBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateCommandBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Pipelines (all the render state and resources in one object) + if (pipelinesState == CVK_RESOURCE_NEEDS_REBUILD) { + displayPipelines = new ArrayList<>(cvkSwapChain.GetImageCount()); + ret = CreatePipelines(cvkSwapChain.GetRenderPassHandle(), displayPipelines); + if (VkFailed(ret)) { return ret; } + + hitTestPipelines = new ArrayList<>(cvkSwapChain.GetImageCount()); + ret = CreatePipelines(cvkSwapChain.GetOffscreenRenderPassHandle(), hitTestPipelines); + if (VkFailed(ret)) { return ret; } + } + } + + return ret; + } + + + // ========================> Tasks <======================== \\ + // NOTE: we effectively have two levels of staging. The second level is pretty + // straightforward, we need the resources we render with to be in the most + // optimal memory possible: resident GPU memory. This memory cannot be written + // by the CPU so we can't update it directly, instead we update staging buffers + // that are both GPU and CPU writable then copy from that into the final GPU + // buffers. + // The first level of staging is required as our staging buffers are VkDevice + // resources and the device may not be initialised when these tasks are called. + // This is the case when a graph is loaded into a new tab, we need to be able + // to process the tasks that load the graph vertices etc before the device is + // ready to create staging buffers. For this reason we update local arrays + // in the BuildArray functions, then copy these to our staging buffers + // during the renderer's display loop (when the rendering lambda of each task + // is executed). This also means we don't need to synchronise these arrays + // created by the visual processor's thread with the staging buffers that have + // a lifespan entirely within the rendering thread (AWT event thread). This + // is possible because the arrays are locals in the task functions and aren't + // modified by the visual processor thread after they've been added to the + // queue of tasks for the renderer to process. + + private Vertex[] BuildVertexArray(final VisualAccess access, int first, int last) { + final int newVertexCount = (last - first) + 1; + if (newVertexCount > 0) { + Vertex vertices[] = new Vertex[newVertexCount]; + for (int i = first; i <= last; ++i) { + final int pos = i + first; + vertices[i] = new Vertex(); + Vertex vertex = vertices[i]; + SetColorInfo(pos, vertex, access); + SetIconIndexes(pos, vertex, access); + } + + return vertices; + } else { + return null; + } + } + + private Position[] BuildPositionArray(final VisualAccess access, int first, int last) { + final int newVertexCount = (last - first) + 1; + if (newVertexCount > 0) { + Position positions[] = new Position[newVertexCount]; + for (int i = 0; i < newVertexCount; ++i) { + final int pos = i + first; + positions[i] = new Position(access.getX(pos), + access.getY(pos), + access.getZ(pos), + access.getRadius(pos), + access.getX2(pos), + access.getY2(pos), + access.getZ2(pos), + access.getRadius(pos)); + } + + return positions; + } else { + return null; + } + } + + private byte[] BuildVertexFlagArray(final VisualAccess access, int first, int last) { + final int newVertexCount = (last - first) + 1; + if (newVertexCount > 0) { + byte vertexFlags[] = new byte[newVertexCount]; + for (int i = first; i <= last; ++i) { + final int pos = i + first; + final boolean isSelected = access.getVertexSelected(pos); + final boolean isDimmed = access.getVertexDimmed(pos); + vertexFlags[i] = (byte)((isDimmed ? DIMMED_BIT : 0) | (isSelected ? SELECTED_BIT : 0)); + } + + return vertexFlags; + } else { + return null; + } + } + + private void RebuildVertexStagingBuffer(Vertex[] vertices) { + final int newSizeBytes = (vertices != null ? vertices.length : 0) * Vertex.BYTES; + final boolean recreate = cvkVertexStagingBuffer == null || newSizeBytes != cvkVertexStagingBuffer.GetBufferSize(); + + if (recreate) { + if (cvkVertexStagingBuffer != null) { + cvkVertexStagingBuffer.Destroy(); + cvkVertexStagingBuffer = null; + } + + if (newSizeBytes > 0) { + cvkVertexStagingBuffer = CVKBuffer.Create(newSizeBytes, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKIconsRenderable.RebuildVertexStagingBuffer cvkVertexStagingBuffer"); + } + } + + if (newSizeBytes > 0) { + UpdateVertexStagingBuffer(vertices, 0, vertices.length - 1); + } + } + + private void UpdateVertexStagingBuffer(Vertex[] vertices, int first, int last) { + CVKAssertNotNull(cvkVertexStagingBuffer); + CVKAssertNotNull(vertices != null); + CVKAssert(vertices.length > 0 && vertices.length > (last - first)); + CVKAssert(last >= 0 && last >= first && first >= 0); + + int offset = first * Vertex.BYTES; + int size = ((last - first) + 1) * Vertex.BYTES; + + ByteBuffer pMemory = cvkVertexStagingBuffer.StartMemoryMap(offset, size); + for (Vertex vertex : vertices) { + vertex.CopyToSequentially(pMemory); + } + cvkVertexStagingBuffer.EndMemoryMap(); + pMemory = null; // now unmapped, do not use + } + + private void RebuildPositionStagingBuffer(Position[] positions) { + final int newSizeBytes = (positions != null ? positions.length : 0) * Position.BYTES; + final boolean recreate = cvkPositionStagingBuffer == null || newSizeBytes != cvkPositionStagingBuffer.GetBufferSize(); + + if (recreate) { + if (cvkPositionStagingBuffer != null) { + cvkPositionStagingBuffer.Destroy(); + cvkPositionStagingBuffer = null; + } + + if (newSizeBytes > 0) { + cvkPositionStagingBuffer = CVKBuffer.Create(newSizeBytes, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKIconsRenderable.RebuildPositionStagingBuffer cvkPositionBuffer"); + } + } + + if (newSizeBytes > 0) { + UpdatePositionStagingBuffer(positions, 0, positions.length - 1); + } + } + + private void UpdatePositionStagingBuffer(Position[] positions, int first, int last) { + CVKAssertNotNull(cvkPositionStagingBuffer); + CVKAssertNotNull(positions != null); + CVKAssert(positions.length > 0 && positions.length > (last - first)); + CVKAssert(last >= 0 && last >= first && first >= 0); + + int offset = first * Position.BYTES; + int size = ((last - first) + 1) * Position.BYTES; + + ByteBuffer pMemory = cvkPositionStagingBuffer.StartMemoryMap(offset, size); + for (Position position : positions) { + position.CopyToSequentially(pMemory); + } + cvkPositionStagingBuffer.EndMemoryMap(); + pMemory = null; // now unmapped, do not use + } + + private void RebuildVertexFlagsStagingBuffer(byte[] vertexFlags) { + final int newSizeBytes = (vertexFlags != null ? vertexFlags.length : 0) * Byte.BYTES; + final boolean recreate = cvkVertexFlagsStagingBuffer == null || newSizeBytes != cvkVertexFlagsStagingBuffer.GetBufferSize(); + + if (recreate) { + if (cvkVertexFlagsStagingBuffer != null) { + cvkVertexFlagsStagingBuffer.Destroy(); + cvkVertexFlagsStagingBuffer = null; + } + + if (newSizeBytes > 0) { + cvkVertexFlagsStagingBuffer = CVKBuffer.Create(newSizeBytes, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKIconsRenderable.RebuildVertexFlagsStagingBuffers cvkVertexFlagsStagingBuffer"); + } + } + + if (newSizeBytes > 0) { + UpdateVertexFlagsStagingBuffer(vertexFlags, 0, vertexFlags.length - 1); + } + } + + private void UpdateVertexFlagsStagingBuffer(byte[] vertexFlags, int first, int last) { + CVKAssert(vertexFlags != null && vertexFlags.length > 0); + CVKAssert(last >= 0 && last >= first && first >= 0); + + int offset = first * Byte.BYTES; + int size = ((last - first) + 1) * Byte.BYTES; + + ByteBuffer pVertexFlagsMemory = cvkVertexFlagsStagingBuffer.StartMemoryMap(offset, size); + pVertexFlagsMemory.put(vertexFlags); + cvkVertexFlagsStagingBuffer.EndMemoryMap(); + pVertexFlagsMemory = null; // now unmapped, do not use + } + + public CVKRenderUpdateTask TaskUpdateIcons(final VisualChange change, final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + // If we have had an update task called before a rebuild task we first have to build + // the staging buffer. Rebuild also if we our vertex count has somehow changed. + final boolean rebuildRequired = cvkVertexStagingBuffer == null || + access.getVertexCount() * Vertex.BYTES != cvkVertexStagingBuffer.GetBufferSize() || + change.isEmpty(); + final int changedVerticeRange[]; + final Vertex vertices[]; + if (rebuildRequired) { + GetLogger().fine("TaskUpdateIcons frame %d: all (%d) verts", cvkVisualProcessor.GetFrameNumber(), access.getVertexCount()); + vertices = BuildVertexArray(access, 0, access.getVertexCount() - 1); + changedVerticeRange = null; + } else { + changedVerticeRange = change.getRange(); + GetLogger().fine("TaskUpdateIcons frame %d: %d verts", cvkVisualProcessor.GetFrameNumber(), (changedVerticeRange[1] - changedVerticeRange[0]) + 1); + vertices = BuildVertexArray(access, changedVerticeRange[0], changedVerticeRange[1]); + } + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + if (rebuildRequired) { + RebuildVertexStagingBuffer(vertices); + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + vertexCount = vertices != null ? vertices.length : 0; + } else if (vertexBuffersState != CVK_RESOURCE_NEEDS_REBUILD) { + UpdateVertexStagingBuffer(vertices, changedVerticeRange[0], changedVerticeRange[1]); + SetVertexBuffersState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + public CVKRenderUpdateTask TaskUpdatePositions(final VisualChange change, final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + // If we have had an update task called before a rebuild task we first have to build + // the staging buffer. Rebuild also if we our vertex count has somehow changed. + final boolean rebuildRequired = cvkPositionStagingBuffer == null || + access.getVertexCount() * Position.BYTES != cvkPositionStagingBuffer.GetBufferSize() || + change.isEmpty(); + final int changedVerticeRange[]; + final Position positions[]; + if (rebuildRequired) { + GetLogger().fine("TaskUpdatePositions frame %d: all (%d) verts", cvkVisualProcessor.GetFrameNumber(), access.getVertexCount()); + positions = BuildPositionArray(access, 0, access.getVertexCount() - 1); + changedVerticeRange = null; + } else { + changedVerticeRange = change.getRange(); + GetLogger().fine("TaskUpdatePositions frame %d: %d verts", cvkVisualProcessor.GetFrameNumber(), (changedVerticeRange[1] - changedVerticeRange[0]) + 1); + positions = BuildPositionArray(access, changedVerticeRange[0], changedVerticeRange[1]); + } + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + if (rebuildRequired) { + RebuildPositionStagingBuffer(positions); + SetPositionBufferState(CVK_RESOURCE_NEEDS_REBUILD); + } else { + UpdatePositionStagingBuffer(positions, changedVerticeRange[0], changedVerticeRange[1]); + if (positionBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetPositionBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + } + }; + } + + public CVKRenderUpdateTask TaskUpdateVertexFlags(final VisualChange change, final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + GetLogger().fine("TaskUpdateVertexFlags frame %d: %d verts", cvkVisualProcessor.GetFrameNumber(), access.getVertexCount()); + + // If we have had an update task called before a rebuild task we first have to build + // the staging buffer. Rebuild also if we our vertex count has somehow changed. + final boolean rebuildRequired = cvkVertexFlagsStagingBuffer == null || + access.getVertexCount() * Position.BYTES != cvkVertexFlagsStagingBuffer.GetBufferSize() || + change.isEmpty(); + final int changedVerticeRange[]; + final byte vertexFlags[]; + if (rebuildRequired) { + vertexFlags = BuildVertexFlagArray(access, 0, access.getVertexCount() - 1); + changedVerticeRange = null; + } else { + changedVerticeRange = change.getRange(); + vertexFlags = BuildVertexFlagArray(access, changedVerticeRange[0], changedVerticeRange[1]); + } + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + if (rebuildRequired) { + RebuildVertexFlagsStagingBuffer(vertexFlags); + SetVertexFlagsBufferState(CVK_RESOURCE_NEEDS_REBUILD); + } else if (vertexFlagsBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + UpdateVertexFlagsStagingBuffer(vertexFlags, changedVerticeRange[0], changedVerticeRange[1]); + SetVertexFlagsBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + public CVKRenderUpdateTask TaskUpdateColours(final VisualChange change, final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + GetLogger().fine("TaskUpdateColours frame %d: %d verts", cvkVisualProcessor.GetFrameNumber(), access.getVertexCount()); + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + if (vertexBuffersState != CVK_RESOURCE_NEEDS_REBUILD) { + SetVertexBuffersState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + public CVKRenderUpdateTask TaskSetHighlightColour(final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + GetLogger().fine("TaskSetHighLightColour frame %d: %d verts", cvkVisualProcessor.GetFrameNumber(), access.getVertexCount()); + final ConstellationColor highlightColour = access.getHighlightColor(); + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + mtxHighlightColour.set(0, 0, highlightColour.getRed()); + mtxHighlightColour.set(1, 1, highlightColour.getGreen()); + mtxHighlightColour.set(2, 2, highlightColour.getBlue()); + + if (geometryUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + public CVKRenderUpdateTask TaskUpdateCamera() { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + UpdateVertexPushConstants(); + }; + } + + + // ========================> Helpers <======================== \\ + + private Vector4i MakeIconIndexes(final int pos, final VisualAccess access) { + CVKAssert(access != null); + CVKAssert(pos < access.getVertexCount()); + + final String foregroundIconName = access.getForegroundIcon(pos); + final String backgroundIconName = access.getBackgroundIcon(pos); + final int foregroundIconIndex = CVKIconTextureAtlas.GetInstance().AddIcon(foregroundIconName); + final int backgroundIconIndex = CVKIconTextureAtlas.GetInstance().AddIcon(backgroundIconName); + + final String nWDecoratorName = access.getNWDecorator(pos); + final String sWDecoratorName = access.getSWDecorator(pos); + final String sEDecoratorName = access.getSEDecorator(pos); + final String nEDecoratorName = access.getNEDecorator(pos); + final int nWDecoratorIndex = nWDecoratorName != null ? CVKIconTextureAtlas.GetInstance().AddIcon(nWDecoratorName) : CVKIconTextureAtlas.TRANSPARENT_ICON_INDEX; + final int sWDecoratorIndex = sWDecoratorName != null ? CVKIconTextureAtlas.GetInstance().AddIcon(sWDecoratorName) : CVKIconTextureAtlas.TRANSPARENT_ICON_INDEX; + final int sEDecoratorIndex = sEDecoratorName != null ? CVKIconTextureAtlas.GetInstance().AddIcon(sEDecoratorName) : CVKIconTextureAtlas.TRANSPARENT_ICON_INDEX; + final int nEDecoratorIndex = nEDecoratorName != null ? CVKIconTextureAtlas.GetInstance().AddIcon(nEDecoratorName) : CVKIconTextureAtlas.TRANSPARENT_ICON_INDEX; + + final int icons = (backgroundIconIndex << ICON_BITS) | (foregroundIconIndex & ICON_MASK); + final int decoratorsWest = (sWDecoratorIndex << ICON_BITS) | (nWDecoratorIndex & ICON_MASK); + final int decoratorsEast = (nEDecoratorIndex << ICON_BITS) | (sEDecoratorIndex & ICON_MASK); + + return new Vector4i(icons, decoratorsWest, decoratorsEast, access.getVertexId(pos)); + } + + private void SetIconIndexes(final int pos, CVKIconsRenderable.Vertex vertex, final VisualAccess access) { + CVKAssert(vertex != null); + vertex.data.set(MakeIconIndexes(pos, access)); + } + + private void SetColorInfo(final int pos, CVKIconsRenderable.Vertex vertex, final VisualAccess access) { + CVKAssert(access != null); + CVKAssert(vertex != null); + CVKAssert(pos < access.getVertexCount()); + + vertex.SetBackgroundIconColour(access.getVertexColor(pos)); + vertex.SetVertexVisibility(access.getVertexVisibility(pos)); + } + + public long GetPositionBufferViewHandle() { return hPositionBufferView; } + + public long GetPositionBufferHandle() { return cvkPositionBuffer != null ? cvkPositionBuffer.GetBufferHandle() : VK_NULL_HANDLE; } + + public long GetPositionBufferSize() { return cvkPositionBuffer != null ? cvkPositionBuffer.GetBufferSize() : 0; } + + public long GetVertexFlagsBufferViewHandle() { return hVertexFlagsBufferView; } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKLinkLabelsRenderable.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKLinkLabelsRenderable.java new file mode 100644 index 0000000000..e816f668f9 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKLinkLabelsRenderable.java @@ -0,0 +1,1714 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.renderables; + +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.*; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.*; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.system.MemoryUtil.*; +import static org.lwjgl.vulkan.VK10.*; +import au.gov.asd.tac.constellation.graph.visual.framework.VisualGraphDefaults; +import au.gov.asd.tac.constellation.utilities.color.ConstellationColor; +import au.gov.asd.tac.constellation.utilities.glyphs.ConnectionGlyphStreamContext; +import au.gov.asd.tac.constellation.utilities.glyphs.GlyphManager; +import au.gov.asd.tac.constellation.utilities.glyphs.GlyphStreamContext; +import au.gov.asd.tac.constellation.utilities.graphics.Matrix44f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector3f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector4f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector4i; +import au.gov.asd.tac.constellation.utilities.text.LabelUtilities; +import au.gov.asd.tac.constellation.utilities.visual.VisualAccess; +import au.gov.asd.tac.constellation.utilities.visual.VisualChange; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool.CVKDescriptorPoolRequirements; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import au.gov.asd.tac.constellation.visual.vulkan.CVKRenderUpdateTask; +import au.gov.asd.tac.constellation.visual.vulkan.CVKSwapChain; +import au.gov.asd.tac.constellation.visual.vulkan.CVKVisualProcessor; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKBuffer; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKCommandBuffer; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKGlyphTextureAtlas; +import java.nio.ByteBuffer; +import java.nio.LongBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkCommandBufferInheritanceInfo; +import org.lwjgl.vulkan.VkDescriptorBufferInfo; +import org.lwjgl.vulkan.VkDescriptorImageInfo; +import org.lwjgl.vulkan.VkDescriptorSetAllocateInfo; +import org.lwjgl.vulkan.VkDescriptorSetLayoutBinding; +import org.lwjgl.vulkan.VkDescriptorSetLayoutCreateInfo; +import org.lwjgl.vulkan.VkPipelineLayoutCreateInfo; +import org.lwjgl.vulkan.VkPushConstantRange; +import org.lwjgl.vulkan.VkVertexInputAttributeDescription; +import org.lwjgl.vulkan.VkVertexInputBindingDescription; +import org.lwjgl.vulkan.VkWriteDescriptorSet; + +/** + * Requirements: + * - labels on connections (these can be multiline) + * - labels above or below icons + * - icon labels can be in any language + * - icon label determined by attribute 'identifier', can be multiline + * - icon label top determined by graph array attribute 'node_labels_top' (array of attributes, first 4 displayed) + * - icon label bottom determined by graph array attribute 'node_labels_bottom' + * - appears only 1 font at present, SansSerif 64 Plain. This is likely configurable + */ + +/** + * What resources do node labels have + * - vs: vertex push constant (camera matrix) + * 0 vs: vertex ubo (label meta data) + * 1 vs: xyzw buffer sampler (icon positions owned by CVKIconsRenderable) + * 2 gs: geometry ubo (proj mtx and render data) + * 3 gs: glyphInfoTexture buffer sampler (glyph coordinates) + * 4 fs: atlas texture sampler (rastered glyphs owned by CVKGlyphTextureAtlas) + */ +public class CVKLinkLabelsRenderable extends CVKRenderable implements GlyphManager.GlyphStream { + private static final int MAX_STAGGERS = 7; + + // Resources recreated with the swap chain (dependent on the image count) + private LongBuffer pDescriptorSets = null; + private List displayCommandBuffers = null; + private List attributeVertexBuffers = null; + private List summaryVertexBuffers = null; + private List vertexUniformBuffers = null; + private List geometryUniformBuffers = null; + + // Push constants + private ByteBuffer vertexPushConstants = null; + private static final int VERTEX_PUSH_CONSTANT_STAGES = VK_SHADER_STAGE_VERTEX_BIT; + private static final int VERTEX_PUSH_CONSTANT_SIZE = Matrix44f.BYTES + Integer.BYTES; + + private final VertexUniformBufferObject vertexUBO = new VertexUniformBufferObject(); + private final GeometryUniformBufferObject geometryUBO = new GeometryUniformBufferObject(); + + // Resources recreated only through user events + private int attributeVertexCount = 0; + private int summaryVertexCount = 0; + + // The vertex lists are transient are should not be used outside the update + // task. They are class members solely so the GlyphStream callback functions + // can add to them. + private List attributeVertices = null; + private List summaryVertices = null; + + private CVKBuffer cvkAttributeVertexStagingBuffer = null; + private CVKBuffer cvkSummaryVertexStagingBuffer = null; + + private CVKBuffer cvkVertexUBStagingBuffer = null; + private CVKBuffer cvkGeometryUBStagingBuffer = null; + private final Vector3f[] labelColours = new Vector3f[LabelUtilities.MAX_LABELS_TO_DRAW]; + private final Vector4i labelSizes = new Vector4i(); + private float currentWidth; + + // Resources we don't own but use and must track so we know when to update + // our descriptors + private long hGlyphAtlasSampler = VK_NULL_HANDLE; + private long hGlyphAtlasImageView = VK_NULL_HANDLE; + private long hGlyphCoordinateBuffer = VK_NULL_HANDLE; + private long hGlyphCoordinateBufferView = VK_NULL_HANDLE; + private long hPositionBuffer = VK_NULL_HANDLE; + private long hPositionBufferView = VK_NULL_HANDLE; + + + // ========================> Classes <======================== \\ + + private static class Vertex { + // This looks a little weird for Java, but LWJGL and JOGL both require + // contiguous memory which is passed to the native GL or VK libraries. + private static final int BYTES = Vector4f.BYTES + Vector4i.BYTES; + private static final int OFFSETOF_GLYPH_DATA = 0; + private static final int OFFSETOF_GRAPH_DATA = Vector4f.BYTES; + private static final int BINDING = 0; + + // [0] label width + // [1..2] x and y offsets of this glyph from the top centre of the line of text + // [3] The visibility of this glyph (constant for a node, but easier to pass in the batch). + private final Vector4f glyphLocationData = new Vector4f(); + + // [0] The index of the low node containing this glyph in the xyzTexture + // [1] The index of the high node containing this glyph in the xyzTexture + // [2] Packed value: The label number in which this glyph occurs, total scale and offset + // [3] Packed value: Glyph index and stagger + private final Vector4i graphLocationData = new Vector4i(); + + public Vertex(float labelWidth, + float xOffset, + float yOffset, + float visibility, + int nodeIndexLow, + int nodeIndexHigh, + int labelNumber, + int totalScale, + int offset, + int glyphIndex, + int stagger, + int linkLabelCount) { + glyphLocationData.a[0] = labelWidth; + glyphLocationData.a[1] = xOffset; + glyphLocationData.a[2] = yOffset; + glyphLocationData.a[3] = visibility; + + graphLocationData.a[0] = nodeIndexLow; + graphLocationData.a[1] = nodeIndexHigh; + graphLocationData.a[2] = (offset << 16) + (totalScale << 2) + labelNumber; + graphLocationData.a[3] = (glyphIndex << 8) + stagger * 256 / (Math.min(linkLabelCount, MAX_STAGGERS) + 1); + } + + public void CopyToSequentially(ByteBuffer buffer) { + buffer.putFloat(glyphLocationData.a[0]); + buffer.putFloat(glyphLocationData.a[1]); + buffer.putFloat(glyphLocationData.a[2]); + buffer.putFloat(glyphLocationData.a[3]); + buffer.putInt(graphLocationData.a[0]); + buffer.putInt(graphLocationData.a[1]); + buffer.putInt(graphLocationData.a[2]); + buffer.putInt(graphLocationData.a[3]); + } + + /** + * A VkVertexInputBindingDescription defines the rate at which data is + * consumed by vertex shader (per vertex or per instance). + * The input rate determines whether to move to the next data entry after + * each vertex or after each instance. + * The binding description also defines the vertex stride, the number of + * bytes that must be stepped from vertex n-1 to vertex n. + * + * @return Binding description for the FPS vertex type + */ + private static VkVertexInputBindingDescription.Buffer GetBindingDescription() { + + VkVertexInputBindingDescription.Buffer bindingDescription = + VkVertexInputBindingDescription.callocStack(1); + + // If we bind multiple vertex buffers with different descriptions + // this is the index of this description occupies in the array of + // bound descriptions. + bindingDescription.binding(BINDING); + bindingDescription.stride(Vertex.BYTES); + bindingDescription.inputRate(VK_VERTEX_INPUT_RATE_VERTEX); + + return bindingDescription; + } + + /** + * A VkVertexInputAttributeDescription describes each element int the + * vertex buffer. + * binding: matches the binding member of VkVertexInputBindingDescription + * location: corresponds to the layout(location = #) in the vertex shader + * for this element (0 for data, 1 for bkgClr). + * format: format the shader will interpret this as. + * offset: bytes from the start of the vertex this attribute starts at + * + * @return + */ + private static VkVertexInputAttributeDescription.Buffer GetAttributeDescriptions() { + + VkVertexInputAttributeDescription.Buffer attributeDescriptions = + VkVertexInputAttributeDescription.callocStack(2); + + // glyphLocationData + VkVertexInputAttributeDescription posDescription = attributeDescriptions.get(0); + posDescription.binding(BINDING); + posDescription.location(0); + posDescription.format(VK_FORMAT_R32G32B32A32_SFLOAT); + posDescription.offset(OFFSETOF_GLYPH_DATA); + + // graphLocationData + VkVertexInputAttributeDescription colorDescription = attributeDescriptions.get(1); + colorDescription.binding(BINDING); + colorDescription.location(1); + colorDescription.format(VK_FORMAT_R32G32B32A32_SINT); + colorDescription.offset(OFFSETOF_GRAPH_DATA); + + return attributeDescriptions.rewind(); + } + } + + @Override + protected VkVertexInputBindingDescription.Buffer GetVertexBindingDescription() { + return Vertex.GetBindingDescription(); + } + + @Override + protected VkVertexInputAttributeDescription.Buffer GetVertexAttributeDescriptions() { + return Vertex.GetAttributeDescriptions(); + } + + private static class VertexUniformBufferObject { + // Each column is a node (attribute or summary) label with the following structure: + // [0..2] rgb colour (note label colours do not have an alpha) + // [3] label size + private final Matrix44f attributeLabelInfo = new Matrix44f(); + private final Matrix44f summaryLabelInfo = new Matrix44f(); + + // Information from the graph's visual state + private float morphMix = 0; + private float visibilityLow = 0; + private float visibilityHigh = 0; + + // The index of the background glyph in the glyphInfo texture + private int backgroundGlyphIndex = 0; + + // Used to draw the label background. + private final Vector4f backgroundColor = new Vector4f(); + + private static Integer padding1 = null; + private static Integer padding2 = null; + + protected VertexUniformBufferObject() { + final ConstellationColor summaryColor = VisualGraphDefaults.DEFAULT_LABEL_COLOR; + summaryLabelInfo.setRow(summaryColor.getRed(), summaryColor.getGreen(), summaryColor.getBlue(), VisualGraphDefaults.DEFAULT_LABEL_SIZE * LabelUtilities.NRADIUS_TO_LABEL_UNITS, 0); + } + + private static int SizeOf() { + if (padding1 == null) { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + final int minAlignment = CVKDevice.GetMinUniformBufferAlignment(); + + // The matrices are 64 bytes each so should line up on a boundary (unless the minimum alignment is huge) + CVKAssert(minAlignment <= (16 * Float.BYTES)); + + int sizeof = Matrix44f.BYTES + // attributeLabelInfo + Matrix44f.BYTES + // summaryLabelInfo + 1 * Float.BYTES + // morphMix + 1 * Float.BYTES + // visibilityLow + 1 * Float.BYTES + // visibilityHigh + 1 * Integer.BYTES; // backgroundGlyphIndex + + int overrun = sizeof % minAlignment; + padding1 = overrun > 0 ? minAlignment - overrun : 0; + + sizeof += padding1 + + Vector4f.BYTES; //backgroundColor + + overrun = sizeof % minAlignment; + padding2 = overrun > 0 ? minAlignment - overrun : 0; + } + + return Matrix44f.BYTES + // attributeLabelInfo + Matrix44f.BYTES + // summaryLabelInfo + 1 * Float.BYTES + // morphMix + 1 * Float.BYTES + // visibilityLow + 1 * Float.BYTES + // visibilityHigh + 1 * Integer.BYTES + // backgroundGlyphIndex + padding1 + + Vector4f.BYTES + //backgroundColor + padding2; + } + + private void CopyTo(ByteBuffer buffer) { + PutMatrix44f(buffer, attributeLabelInfo); + PutMatrix44f(buffer, summaryLabelInfo); + + buffer.putFloat(morphMix); + buffer.putFloat(visibilityLow); + buffer.putFloat(visibilityHigh); + buffer.putInt(backgroundGlyphIndex); + + for (int i = 0; i < padding1; ++i) { + buffer.put((byte)0); + } + + for (int i = 0; i < Vector4f.LENGTH; ++i) { + buffer.putFloat(backgroundColor.a[i]); + } + + for (int i = 0; i < padding2; ++i) { + buffer.put((byte)0); + } + } + } + + private static class GeometryUniformBufferObject { + // Matrix to convert from camera coordinates to scene coordinates. + private final Matrix44f pMatrix = new Matrix44f(); + + // The scaling factor to convert from texture coordinates to world unit coordinates + private float widthScalingFactor; + private float heightScalingFactor; + + // Used to draw the connection indicator on the label background. + private final Vector4f highlightColor = new Vector4f(); + + private static Integer padding1 = null; + private static Integer padding2 = null; + + + private static int SizeOf() { + if (padding1 == null) { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + final int minAlignment = CVKDevice.GetMinUniformBufferAlignment(); + + // The matrices are 64 bytes each so should line up on a boundary (unless the minimum alignment is huge) + CVKAssert(minAlignment <= (16 * Float.BYTES)); + + int sizeof = Matrix44f.BYTES + // pMatrix + 1 * Float.BYTES + // widthScalingFactor + 1 * Float.BYTES; // heightScalingFactor + + int overrun = sizeof % minAlignment; + padding1 = overrun > 0 ? minAlignment - overrun : 0; + + sizeof += padding1 + + Vector4f.LENGTH * Float.BYTES; //highlightColor + + overrun = sizeof % minAlignment; + padding2 = overrun > 0 ? minAlignment - overrun : 0; + } + + return Matrix44f.BYTES + // pMatrix + 1 * Float.BYTES + // widthScalingFactor + 1 * Float.BYTES + // heightScalingFactor + padding1 + + Vector4f.BYTES + //highlightColor + padding2; + } + + private void CopyTo(ByteBuffer buffer) { + for (int iRow = 0; iRow < 4; ++iRow) { + for (int iCol = 0; iCol < 4; ++iCol) { + buffer.putFloat(pMatrix.get(iRow, iCol)); + } + } + buffer.putFloat(widthScalingFactor); + buffer.putFloat(heightScalingFactor); + + for (int i = 0; i < padding1; ++i) { + buffer.put((byte)0); + } + + for (int i = 0; i < Vector4f.LENGTH; ++i) { + buffer.putFloat(highlightColor.a[i]); + } + + for (int i = 0; i < padding2; ++i) { + buffer.put((byte)0); + } + } + } + + + // ========================> Shaders <======================== \\ + + @Override + protected String GetVertexShaderName() { return "ConnectionLabel.vs"; } + + @Override + protected String GetGeometryShaderName() { return "Label.gs"; } + + @Override + protected String GetFragmentShaderName() { return "Label.fs"; } + + + // ========================> Lifetime <======================== \\ + + public CVKLinkLabelsRenderable(CVKVisualProcessor visualProcessor) { + super(visualProcessor); + } + + private void CreateUBOStagingBuffers() { + cvkVertexUBStagingBuffer = CVKBuffer.Create(VertexUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKLinkLabelsRenderable.CreateUBOStagingBuffers cvkVertexUBStagingBuffer"); + cvkGeometryUBStagingBuffer = CVKBuffer.Create(GeometryUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKLinkLabelsRenderable.CreateUBOStagingBuffers cvkGeometryUBStagingBuffer"); + } + + @Override + public int Initialise() { + int ret = super.Initialise(); + if (VkFailed(ret)) { return ret; } + + CreatePushConstants(); + + ret = CreateDescriptorLayout(); + if (VkFailed(ret)) { return ret; } + + ret = CreatePipelineLayout(); + if (VkFailed(ret)) { return ret; } + + CreateUBOStagingBuffers(); + + return ret; + } + + private void DestroyStagingBuffers() { + if (cvkAttributeVertexStagingBuffer != null) { + cvkAttributeVertexStagingBuffer.Destroy(); + cvkAttributeVertexStagingBuffer = null; + } + if (cvkSummaryVertexStagingBuffer != null) { + cvkSummaryVertexStagingBuffer.Destroy(); + cvkSummaryVertexStagingBuffer = null; + } + if (cvkVertexUBStagingBuffer != null) { + cvkVertexUBStagingBuffer.Destroy(); + cvkVertexUBStagingBuffer = null; + } + if (cvkGeometryUBStagingBuffer != null) { + cvkGeometryUBStagingBuffer.Destroy(); + cvkGeometryUBStagingBuffer = null; + } + } + + @Override + public void Destroy() { + DestroyVertexBuffers(); + DestroyVertexUniformBuffers(); + DestroyGeometryUniformBuffers(); + DestroyDescriptorSets(); + DestroyDescriptorLayout(); + DestroyPipelines(); + DestroyPipelineLayout(); + DestroyCommandBuffers(); + DestroyStagingBuffers(); + DestroyPushConstants(); + + // Reset our cached handles + hGlyphAtlasSampler = VK_NULL_HANDLE; + hGlyphAtlasImageView = VK_NULL_HANDLE; + hGlyphCoordinateBufferView = VK_NULL_HANDLE; + hPositionBufferView = VK_NULL_HANDLE; + + CVKAssert(attributeVertexBuffers == null); + CVKAssert(vertexUniformBuffers == null); + CVKAssert(geometryUniformBuffers == null); + CVKAssert(pDescriptorSets == null); + CVKAssert(hDescriptorLayout == VK_NULL_HANDLE); + CVKAssert(displayCommandBuffers == null); + CVKAssert(displayPipelines == null); + CVKAssert(hPipelineLayout == VK_NULL_HANDLE); + } + + + // ========================> Swap chain <======================== \\ + + @Override + protected int DestroySwapChainResources() { + this.cvkSwapChain = null; + + // We only need to recreate these resources if the number of images in + // the swapchain changes or if this is the first call after the initial + // swapchain is created. + if (displayPipelines != null && swapChainImageCountChanged) { + DestroyVertexBuffers(); + DestroyVertexUniformBuffers(); + DestroyGeometryUniformBuffers(); + DestroyDescriptorSets(); + DestroyCommandBuffers(); + DestroyPipelines(); + } + + return VK_SUCCESS; + } + + @Override + public int SetNewSwapChain(CVKSwapChain newSwapChain) { + int ret = super.SetNewSwapChain(newSwapChain); + if (VkFailed(ret)) { return ret; } + + if (swapChainImageCountChanged) { + // The number of images has changed, we need to rebuild all image + // buffered resources + SetVertexUniformBufferState(CVK_RESOURCE_NEEDS_REBUILD); + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_REBUILD); + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + SetCommandBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + SetDescriptorSetsState(CVK_RESOURCE_NEEDS_REBUILD); + SetPipelinesState(CVK_RESOURCE_NEEDS_REBUILD); + } else { + // View frustum and projection matrix likely have changed. We don't + // need to rebuild our displayPipelines as the frustum is set by dynamic + // state in RecordDisplayCommandBuffer + if (geometryUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + } + + return ret; + } + + + // ========================> Vertex buffers <======================== \\ + + private int CreateVertexBuffers() { + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + + // We can only create vertex buffers if we have something to put in them + boolean needsUpdate = false; + if (cvkAttributeVertexStagingBuffer != null && cvkAttributeVertexStagingBuffer.GetBufferSize() > 0) { + int imageCount = cvkSwapChain.GetImageCount(); + attributeVertexBuffers = new ArrayList<>(); + + for (int i = 0; i < imageCount; ++i) { + CVKBuffer cvkVertexBuffer = CVKBuffer.Create(cvkAttributeVertexStagingBuffer.GetBufferSize(), + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKLinkLabelsRenderable cvkAttributeVertexBuffer %d", i)); + attributeVertexBuffers.add(cvkVertexBuffer); + } + needsUpdate = true; + } + if (cvkSummaryVertexStagingBuffer != null && cvkSummaryVertexStagingBuffer.GetBufferSize() > 0) { + int imageCount = cvkSwapChain.GetImageCount(); + summaryVertexBuffers = new ArrayList<>(); + + for (int i = 0; i < imageCount; ++i) { + CVKBuffer cvkVertexBuffer = CVKBuffer.Create(cvkSummaryVertexStagingBuffer.GetBufferSize(), + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKLinkLabelsRenderable cvkSummaryVertexBuffer %d", i)); + summaryVertexBuffers.add(cvkVertexBuffer); + } + needsUpdate = true; + } + + if (needsUpdate) { + return UpdateVertexBuffers(); + } + + return ret; + } + + private int UpdateVertexBuffers() { + cvkVisualProcessor.VerifyInRenderThread(); + int ret = VK_SUCCESS; + + if (cvkAttributeVertexStagingBuffer != null && cvkAttributeVertexStagingBuffer.GetBufferSize() > 0) { + CVKAssert(attributeVertexBuffers != null); + CVKAssert(cvkAttributeVertexStagingBuffer.GetBufferSize() == attributeVertexBuffers.get(0).GetBufferSize()); + + for (int i = 0; i < attributeVertexBuffers.size(); ++i) { + CVKBuffer cvkVertexBuffer = attributeVertexBuffers.get(i); + ret = cvkVertexBuffer.CopyFrom(cvkAttributeVertexStagingBuffer); + if (VkFailed(ret)) { return ret; } + } + } + + if (cvkSummaryVertexStagingBuffer != null && cvkSummaryVertexStagingBuffer.GetBufferSize() > 0) { + CVKAssert(summaryVertexBuffers != null); + CVKAssert(cvkSummaryVertexStagingBuffer.GetBufferSize() == summaryVertexBuffers.get(0).GetBufferSize()); + + for (int i = 0; i < summaryVertexBuffers.size(); ++i) { + CVKBuffer cvkVertexBuffer = summaryVertexBuffers.get(i); + ret = cvkVertexBuffer.CopyFrom(cvkSummaryVertexStagingBuffer); + if (VkFailed(ret)) { return ret; } + } + } + + // Note the staging buffers are not freed as we can simplify the update tasks + // by just updating it and then copying it over again during ProcessRenderTasks(). + SetVertexBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public int GetVertexCount() { + return cvkVisualProcessor.GetDrawFlags().drawConnections() && + cvkVisualProcessor.GetDrawFlags().drawConnectionLabels() ? + attributeVertexCount + summaryVertexCount : 0; + } + + private void DestroyVertexBuffers() { + if (attributeVertexBuffers != null) { + attributeVertexBuffers.forEach(el -> {el.Destroy();}); + attributeVertexBuffers.clear(); + attributeVertexBuffers = null; + } + if (summaryVertexBuffers != null) { + summaryVertexBuffers.forEach(el -> {el.Destroy();}); + summaryVertexBuffers.clear(); + summaryVertexBuffers = null; + } + } + + + // ========================> Uniform buffers <======================== \\ + + private int CreateVertexUniformBuffers(MemoryStack stack) { + CVKAssert(cvkSwapChain != null); + CVKAssert(vertexUniformBuffers == null); + + vertexUniformBuffers = new ArrayList<>(); + for (int i = 0; i < cvkSwapChain.GetImageCount(); ++i) { + CVKBuffer vertexUniformBuffer = CVKBuffer.Create(VertexUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKLinkLabelsRenderable vertexUniformBuffer %d", i)); + vertexUniformBuffers.add(vertexUniformBuffer); + } + return UpdateVertexUniformBuffers(stack); + } + + private int UpdateVertexUniformBuffers(MemoryStack stack) { + CVKAssert(cvkSwapChain != null); + CVKAssert(cvkVertexUBStagingBuffer != null); + CVKAssert(vertexUniformBuffers != null); + CVKAssert(vertexUniformBuffers.size() > 0); + + int ret = VK_SUCCESS; + + // While this never changes we won't know it when the UBO is created as the + // background glyph won't be added until the first render as we need the + // device to be initialised. + // TODO: check this is correct, it comes from the glyphmanager not the atlas + vertexUBO.backgroundGlyphIndex = CVKGlyphTextureAtlas.BACKGROUND_GLYPH_INDEX; + + vertexUBO.morphMix = cvkVisualProcessor.getDisplayCamera().getMix(); + + // TODO: replace with constants. In the JOGL version these were in a static var CAMERA that never changed + vertexUBO.visibilityLow = cvkVisualProcessor.getDisplayCamera().getVisibilityLow(); + vertexUBO.visibilityHigh = cvkVisualProcessor.getDisplayCamera().getVisibilityHigh(); + + // Staging buffer so our VBO can be device local (most performant memory) + ByteBuffer pMemory = cvkVertexUBStagingBuffer.StartMemoryMap(0, VertexUniformBufferObject.SizeOf()); + { + vertexUBO.CopyTo(pMemory); + } + cvkVertexUBStagingBuffer.EndMemoryMap(); + pMemory = null; + + // Copy the staging buffer into the uniform buffer on the device + final int imageCount = cvkSwapChain.GetImageCount(); + for (int i = 0; i < imageCount; ++i) { + ret = vertexUniformBuffers.get(i).CopyFrom(cvkVertexUBStagingBuffer); + if (VkFailed(ret)) { return ret; } + } + + // We are done, reset the resource state + SetVertexUniformBufferState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private void DestroyVertexUniformBuffers() { + if (vertexUniformBuffers != null) { + vertexUniformBuffers.forEach(el -> {el.Destroy();}); + vertexUniformBuffers = null; + } + } + + private int CreateGeometryUniformBuffers(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + CVKAssert(geometryUniformBuffers == null); + + geometryUniformBuffers = new ArrayList<>(); + for (int i = 0; i < cvkSwapChain.GetImageCount(); ++i) { + CVKBuffer geometryUniformBuffer = CVKBuffer.Create(GeometryUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKLinkLabelsRenderable geometryUniformBuffer %d", i)); + geometryUniformBuffers.add(geometryUniformBuffer); + } + return UpdateGeometryUniformBuffers(stack); + } + + private int UpdateGeometryUniformBuffers(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkGeometryUBStagingBuffer); + CVKAssertNotNull(geometryUniformBuffers); + CVKAssert(geometryUniformBuffers.size() > 0); + + int ret = VK_SUCCESS; + + geometryUBO.pMatrix.set(cvkVisualProcessor.GetProjectionMatrix()); + geometryUBO.widthScalingFactor = CVKGlyphTextureAtlas.GetInstance().GetWidthScalingFactor(); + geometryUBO.heightScalingFactor = CVKGlyphTextureAtlas.GetInstance().GetWidthScalingFactor(); + + // Staging buffer so our VBO can be device local (most performant memory) + ByteBuffer pMemory = cvkGeometryUBStagingBuffer.StartMemoryMap(0, GeometryUniformBufferObject.SizeOf()); + { + geometryUBO.CopyTo(pMemory); + } + cvkGeometryUBStagingBuffer.EndMemoryMap(); + pMemory = null; + + // Copy the staging buffer into the uniform buffer on the device + final int imageCount = cvkSwapChain.GetImageCount(); + for (int i = 0; i < imageCount; ++i) { + ret = geometryUniformBuffers.get(i).CopyFrom(cvkGeometryUBStagingBuffer); + if (VkFailed(ret)) { return ret; } + } + + // We are done, reset the resource state + SetGeometryUniformBufferState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private void DestroyGeometryUniformBuffers() { + if (geometryUniformBuffers != null) { + geometryUniformBuffers.forEach(el -> {el.Destroy();}); + geometryUniformBuffers = null; + } + } + + + // ========================> Push constants <======================== \\ + + private int CreatePushConstants() { + // Initialise push constants mvMatrix to identity mtx + vertexPushConstants = memAlloc(VERTEX_PUSH_CONSTANT_SIZE); + PutMatrix44f(vertexPushConstants, IDENTITY_44F); + + // Initialise attribute summary flag to true (attribute) + vertexPushConstants.putInt(VK_TRUE); + + vertexPushConstants.flip(); + + return VK_SUCCESS; + } + + private void UpdatePushConstants(){ + CVKAssertNotNull(cvkSwapChain); + + vertexPushConstants.clear(); + + // Update MV Matrix + PutMatrix44f(vertexPushConstants, cvkVisualProcessor.getDisplayModelViewMatrix()); + + // Reset attribute summary flag + vertexPushConstants.putInt(VK_TRUE); + + vertexPushConstants.flip(); + } + + private void DestroyPushConstants() { + if (vertexPushConstants != null) { + memFree(vertexPushConstants); + vertexPushConstants = null; + } + } + + + // ========================> Command buffers <======================== \\ + + public int CreateCommandBuffers(){ + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + int imageCount = cvkSwapChain.GetImageCount(); + + displayCommandBuffers = new ArrayList<>(imageCount); + + for (int i = 0; i < imageCount; ++i) { + CVKCommandBuffer buffer = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_SECONDARY, GetLogger(), String.format("CVKLinkLabelsRenderable %d", i)); + displayCommandBuffers.add(buffer); + } + + SetCommandBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public VkCommandBuffer GetDisplayCommandBuffer(int imageIndex) { + return displayCommandBuffers.get(imageIndex).GetVKCommandBuffer(); + } + + @Override + public int RecordDisplayCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int imageIndex){ + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(CVKDevice.GetCommandPoolHandle()); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + CVKCommandBuffer commandBuffer = displayCommandBuffers.get(imageIndex); + CVKAssert(commandBuffer != null); + CVKAssert(displayPipelines.get(imageIndex) != null); + + commandBuffer.BeginRecordSecondary(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, inheritanceInfo); + + commandBuffer.SetViewPort(cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight()); + commandBuffer.SetScissor(cvkVisualProcessor.GetCanvas().GetCurrentSurfaceExtent()); + + commandBuffer.BindGraphicsPipeline(displayPipelines.get(imageIndex)); + + // Push MV matrix to the shader + commandBuffer.PushConstants(hPipelineLayout, VERTEX_PUSH_CONSTANT_STAGES, 0, vertexPushConstants); + + commandBuffer.BindGraphicsDescriptorSets(hPipelineLayout, pDescriptorSets.get(imageIndex)); + + + if (attributeVertexCount > 0) { + vertexPushConstants.putInt(Matrix44f.BYTES, VK_TRUE); + commandBuffer.PushConstants(hPipelineLayout, VERTEX_PUSH_CONSTANT_STAGES, 0, vertexPushConstants); + commandBuffer.BindVertexInput(attributeVertexBuffers.get(imageIndex).GetBufferHandle()); + commandBuffer.Draw(attributeVertexCount); + } + if (summaryVertexCount > 0) { + vertexPushConstants.putInt(Matrix44f.BYTES, VK_FALSE); + commandBuffer.PushConstants(hPipelineLayout, VERTEX_PUSH_CONSTANT_STAGES, 0, vertexPushConstants); + commandBuffer.BindVertexInput(summaryVertexBuffers.get(imageIndex).GetBufferHandle()); + commandBuffer.Draw(summaryVertexCount); + } + + ret = commandBuffer.FinishRecord(); + if (VkFailed(ret)) { return ret; } + + return ret; + } + + private void DestroyCommandBuffers() { + if (displayCommandBuffers != null) { + displayCommandBuffers.forEach(el -> {el.Destroy();}); + displayCommandBuffers.clear(); + displayCommandBuffers = null; + } + } + + + // ========================> Descriptors <======================== \\ + + private int CreateDescriptorLayout() { + int ret; + + try (MemoryStack stack = stackPush()) { + VkDescriptorSetLayoutBinding.Buffer bindings = VkDescriptorSetLayoutBinding.callocStack(5, stack); + + // 0: Vertex uniform buffer + VkDescriptorSetLayoutBinding vertexUBDSLB = bindings.get(0); + vertexUBDSLB.binding(0); + vertexUBDSLB.descriptorCount(1); + vertexUBDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + vertexUBDSLB.pImmutableSamplers(null); + vertexUBDSLB.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + + // 1: Vertex samplerBuffer (position buffer) + VkDescriptorSetLayoutBinding vertexSamplerDSLB = bindings.get(1); + vertexSamplerDSLB.binding(1); + vertexSamplerDSLB.descriptorCount(1); + vertexSamplerDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + vertexSamplerDSLB.pImmutableSamplers(null); + vertexSamplerDSLB.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + + // 2: Geometry uniform buffer + VkDescriptorSetLayoutBinding geometryUBDSLB = bindings.get(2); + geometryUBDSLB.binding(2); + geometryUBDSLB.descriptorCount(1); + geometryUBDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + geometryUBDSLB.pImmutableSamplers(null); + geometryUBDSLB.stageFlags(VK_SHADER_STAGE_GEOMETRY_BIT); + + // 3: Geometry samplerBuffer (glyph coordinate buffer) + VkDescriptorSetLayoutBinding geometrySamplerDSLB = bindings.get(3); + geometrySamplerDSLB.binding(3); + geometrySamplerDSLB.descriptorCount(1); + geometrySamplerDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + geometrySamplerDSLB.pImmutableSamplers(null); + geometrySamplerDSLB.stageFlags(VK_SHADER_STAGE_GEOMETRY_BIT); + + // 4: Fragment sampler2Darray (atlas) + VkDescriptorSetLayoutBinding fragmentSamplerDSLB = bindings.get(4); + fragmentSamplerDSLB.binding(4); + fragmentSamplerDSLB.descriptorCount(1); + fragmentSamplerDSLB.descriptorType(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + fragmentSamplerDSLB.pImmutableSamplers(null); + fragmentSamplerDSLB.stageFlags(VK_SHADER_STAGE_FRAGMENT_BIT); + + VkDescriptorSetLayoutCreateInfo layoutInfo = VkDescriptorSetLayoutCreateInfo.callocStack(stack); + layoutInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO); + layoutInfo.pBindings(bindings); + + LongBuffer pDescriptorSetLayout = stack.mallocLong(1); + + ret = vkCreateDescriptorSetLayout(CVKDevice.GetVkDevice(), layoutInfo, null, pDescriptorSetLayout); + if (VkSucceeded(ret)) { + hDescriptorLayout = pDescriptorSetLayout.get(0); + GetLogger().info("CVKLinkLabelsRenderable created hDescriptorLayout: 0x%016X", hDescriptorLayout); + } + } + return ret; + } + + private void DestroyDescriptorLayout() { + GetLogger().info("CVKLinkLabelsRenderable destroying hDescriptorLayout: 0x%016X", hDescriptorLayout); + vkDestroyDescriptorSetLayout(CVKDevice.GetVkDevice(), hDescriptorLayout, null); + hDescriptorLayout = VK_NULL_HANDLE; + } + + private int CreateDescriptorSets(MemoryStack stack) { + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + // The same layout is used for each descriptor set (each descriptor set is + // identical but allow the GPU and CPU to desynchronise. + final int imageCount = cvkSwapChain.GetImageCount(); + LongBuffer layouts = stack.mallocLong(imageCount); + for (int i = 0; i < imageCount; ++i) { + layouts.put(i, hDescriptorLayout); + } + + VkDescriptorSetAllocateInfo allocInfo = VkDescriptorSetAllocateInfo.callocStack(stack); + allocInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO); + allocInfo.descriptorPool(cvkDescriptorPool.GetDescriptorPoolHandle()); + allocInfo.pSetLayouts(layouts); + + // Allocate the descriptor sets from the descriptor pool, they'll be unitialised + pDescriptorSets = MemoryUtil.memAllocLong(imageCount); + ret = vkAllocateDescriptorSets(CVKDevice.GetVkDevice(), allocInfo, pDescriptorSets); + if (VkFailed(ret)) { return ret; } + + for (int i = 0; i < pDescriptorSets.capacity(); ++i) { + GetLogger().info("CVKLinkLabelsRenderable allocated hDescriptorSet %d: 0x%016X", i, pDescriptorSets.get(i)); + } + + return UpdateDescriptorSets(stack); + } + + // TODO: do we gain anything by having buffered UBOs? + private int UpdateDescriptorSets(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(pDescriptorSets); + CVKAssert(pDescriptorSets.capacity() > 0); + CVKAssertNotNull(vertexUniformBuffers); + CVKAssert(vertexUniformBuffers.size() > 0); + CVKAssertNotNull(geometryUniformBuffers); + CVKAssert(geometryUniformBuffers.size() > 0); + + int ret = VK_SUCCESS; + + final int imageCount = cvkSwapChain.GetImageCount(); + + // Cache atlas handles so we know when to recreate descriptors + hGlyphAtlasSampler = CVKGlyphTextureAtlas.GetInstance().GetAtlasSamplerHandle(); + hGlyphAtlasImageView = CVKGlyphTextureAtlas.GetInstance().GetAtlasImageViewHandle(); + hGlyphCoordinateBuffer = CVKGlyphTextureAtlas.GetInstance().GetCoordinateBufferHandle(); + hGlyphCoordinateBufferView = CVKGlyphTextureAtlas.GetInstance().GetCoordinateBufferViewHandle(); + hPositionBuffer = cvkVisualProcessor.GetPositionBufferHandle(); + hPositionBufferView = cvkVisualProcessor.GetPositionBufferViewHandle(); + CVKAssertNotNull(hGlyphAtlasSampler); + CVKAssertNotNull(hGlyphAtlasImageView); + CVKAssertNotNull(hGlyphCoordinateBuffer); + CVKAssertNotNull(hGlyphCoordinateBufferView); + CVKAssertNotNull(hPositionBuffer); + CVKAssertNotNull(hPositionBufferView); + + // - Descriptor info structs - + // We create these to describe the different resources we want to address + // in shaders. We have one info struct per resource. We then create a + // write descriptor set structure for each resource for each image. For + // buffered resources like the the uniform buffers we wait to set the + // buffer resource until the image loop below. + + // Struct for the uniform buffer used by NodeLabel.vs + VkDescriptorBufferInfo.Buffer vertexUniformBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + // vertexUniformBufferInfo.buffer is set per imageIndex + vertexUniformBufferInfo.offset(0); + vertexUniformBufferInfo.range(VertexUniformBufferObject.SizeOf()); + + // Struct for texel buffer (positions) used by NodeLabel.vs + VkDescriptorBufferInfo.Buffer positionsTexelBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + positionsTexelBufferInfo.buffer(hPositionBuffer); + positionsTexelBufferInfo.offset(0); + positionsTexelBufferInfo.range(cvkVisualProcessor.GetPositionBufferSize()); + + // Struct for the uniform buffer used by Label.gs + VkDescriptorBufferInfo.Buffer geometryUniformBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + // geometryBufferInfo.buffer is set per imageIndex + geometryUniformBufferInfo.offset(0); + geometryUniformBufferInfo.range(GeometryUniformBufferObject.SizeOf()); + + // Struct for texel buffer (glyph coordinates) used by Label.gs + VkDescriptorBufferInfo.Buffer glyphCoordinateTexelBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + glyphCoordinateTexelBufferInfo.buffer(hGlyphCoordinateBuffer); + glyphCoordinateTexelBufferInfo.offset(0); + glyphCoordinateTexelBufferInfo.range(CVKGlyphTextureAtlas.GetInstance().GetCoordinateBufferSize()); + + // Struct for the size of the image sampler (atlas) used by Label.fs + VkDescriptorImageInfo.Buffer imageInfo = VkDescriptorImageInfo.callocStack(1, stack); + imageInfo.imageLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + imageInfo.imageView(hGlyphAtlasImageView); + imageInfo.sampler(hGlyphAtlasSampler); + + // We need 5 write descriptors, 2 for uniform buffers, 2 for texel buffers and 1 for texture sampler + VkWriteDescriptorSet.Buffer descriptorWrites = VkWriteDescriptorSet.callocStack(5, stack); + + // Vertex uniform buffer + VkWriteDescriptorSet vertexUBDescriptorWrite = descriptorWrites.get(0); + vertexUBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + vertexUBDescriptorWrite.dstBinding(0); + vertexUBDescriptorWrite.dstArrayElement(0); + vertexUBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + vertexUBDescriptorWrite.descriptorCount(1); + vertexUBDescriptorWrite.pBufferInfo(vertexUniformBufferInfo); + + // Vertex texel buffer (positions) + VkWriteDescriptorSet positionsTBDescriptorWrite = descriptorWrites.get(1); + positionsTBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + positionsTBDescriptorWrite.dstBinding(1); + positionsTBDescriptorWrite.dstArrayElement(0); + positionsTBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + positionsTBDescriptorWrite.descriptorCount(1); + positionsTBDescriptorWrite.pBufferInfo(positionsTexelBufferInfo); + positionsTBDescriptorWrite.pTexelBufferView(stack.longs(hPositionBufferView)); + + // Geometry uniform buffer + VkWriteDescriptorSet geometryUBDescriptorWrite = descriptorWrites.get(2); + geometryUBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + geometryUBDescriptorWrite.dstBinding(2); + geometryUBDescriptorWrite.dstArrayElement(0); + geometryUBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + geometryUBDescriptorWrite.descriptorCount(1); + geometryUBDescriptorWrite.pBufferInfo(geometryUniformBufferInfo); + + // Geometry texel buffer (glyph coordinates) + VkWriteDescriptorSet vertexFlagsTBDescriptorWrite = descriptorWrites.get(3); + vertexFlagsTBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + vertexFlagsTBDescriptorWrite.dstBinding(3); + vertexFlagsTBDescriptorWrite.dstArrayElement(0); + vertexFlagsTBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + vertexFlagsTBDescriptorWrite.descriptorCount(1); + vertexFlagsTBDescriptorWrite.pBufferInfo(glyphCoordinateTexelBufferInfo); + vertexFlagsTBDescriptorWrite.pTexelBufferView(stack.longs(hGlyphCoordinateBufferView)); + + // Fragment image (atlas) sampler + VkWriteDescriptorSet atlasSamplerDescriptorWrite = descriptorWrites.get(4); + atlasSamplerDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + atlasSamplerDescriptorWrite.dstBinding(4); + atlasSamplerDescriptorWrite.dstArrayElement(0); + atlasSamplerDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + atlasSamplerDescriptorWrite.descriptorCount(1); + atlasSamplerDescriptorWrite.pImageInfo(imageInfo); + + for (int i = 0; i < imageCount; ++i) { + // Update the buffered resource buffers + vertexUniformBufferInfo.buffer(vertexUniformBuffers.get(i).GetBufferHandle()); + geometryUniformBufferInfo.buffer(geometryUniformBuffers.get(i).GetBufferHandle()); + + // Set the descriptor set we're updating in each write struct + long descriptorSet = pDescriptorSets.get(i); + descriptorWrites.forEach(el -> {el.dstSet(descriptorSet);}); + + // Update the descriptors with a write and no copy + GetLogger().info("CVKLinkLabelsRenderable updating descriptorSet: 0x%016X", descriptorSet); + vkUpdateDescriptorSets(CVKDevice.GetVkDevice(), descriptorWrites, null); + } + + SetDescriptorSetsState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private int DestroyDescriptorSets() { + int ret = VK_SUCCESS; + + if (pDescriptorSets != null) { + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(cvkDescriptorPool.GetDescriptorPoolHandle()); + GetLogger().fine("CVKLinkLabelsRenderable returning %d descriptor sets to the pool", pDescriptorSets.capacity()); + + for (int i = 0; i < pDescriptorSets.capacity(); ++i) { + GetLogger().info("CVKLinkLabelsRenderable freeing hDescriptorSet %d: 0x%016X", i, pDescriptorSets.get(i)); + } + + // After calling vkFreeDescriptorSets, all descriptor sets in pDescriptorSets are invalid. + ret = vkFreeDescriptorSets(CVKDevice.GetVkDevice(), cvkDescriptorPool.GetDescriptorPoolHandle(), pDescriptorSets); + pDescriptorSets = null; + checkVKret(ret); + } + + return ret; + } + + @Override + public int DestroyDescriptorPoolResources() { + int ret = VK_SUCCESS; + + if (cvkDescriptorPool != null) { + return DestroyDescriptorSets(); + } + + return ret; + } + + @Override + public void IncrementDescriptorTypeRequirements(CVKDescriptorPoolRequirements reqs, CVKDescriptorPoolRequirements perImageReqs) { + // NodeLabel.vs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]; + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER]; + + // Label.gs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]; + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER]; + + // Label.fs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER]; + + // One set per image + ++perImageReqs.poolDesciptorSetCount; + } + + @Override + public int SetNewDescriptorPool(CVKDescriptorPool newDescriptorPool) { + int ret = super.SetNewDescriptorPool(newDescriptorPool); + if (VkFailed(ret)) { return ret; } + SetDescriptorSetsState(CVK_RESOURCE_NEEDS_REBUILD); + return ret; + } + + + // ========================> Pipelines <======================== \\ + + private int CreatePipelineLayout() { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(hDescriptorLayout); + + int ret; + try (MemoryStack stack = stackPush()) { + VkPushConstantRange.Buffer pushConstantRange; + pushConstantRange = VkPushConstantRange.calloc(1); + pushConstantRange.get(0).stageFlags(VERTEX_PUSH_CONSTANT_STAGES); + pushConstantRange.get(0).size(VERTEX_PUSH_CONSTANT_SIZE); + pushConstantRange.get(0).offset(0); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = VkPipelineLayoutCreateInfo.callocStack(stack); + pipelineLayoutInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO); + pipelineLayoutInfo.pSetLayouts(stack.longs(hDescriptorLayout)); + pipelineLayoutInfo.pPushConstantRanges(pushConstantRange); + LongBuffer pPipelineLayout = stack.longs(VK_NULL_HANDLE); + ret = vkCreatePipelineLayout(CVKDevice.GetVkDevice(), pipelineLayoutInfo, null, pPipelineLayout); + if (VkFailed(ret)) { return ret; } + hPipelineLayout = pPipelineLayout.get(0); + CVKAssert(hPipelineLayout != VK_NULL_HANDLE); + } + return ret; + } + + private void DestroyPipelineLayout() { + if (hPipelineLayout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(CVKDevice.GetVkDevice(), hPipelineLayout, null); + hPipelineLayout = VK_NULL_HANDLE; + } + } + + + // ========================> Display <======================== \\ + + @Override + public boolean NeedsDisplayUpdate() { + // Race condition: icons owns the position buffer and the update task (on + // the visual processor thread) may not yet be complete. If this happens + // before the first update is finished icons will have a vertexCount of + // 0 so it won't be able to create the position buffer yet. If we can't + // get a handle to the current position buffer then we just skip updating + // until we can. + if (cvkVisualProcessor.GetPositionBufferHandle() == VK_NULL_HANDLE) { + return false; + } + + if (hPositionBuffer != cvkVisualProcessor.GetPositionBufferHandle() || + hPositionBufferView != cvkVisualProcessor.GetPositionBufferViewHandle() || + hGlyphAtlasSampler != CVKGlyphTextureAtlas.GetInstance().GetAtlasSamplerHandle() || + hGlyphAtlasImageView != CVKGlyphTextureAtlas.GetInstance().GetAtlasImageViewHandle() || + hGlyphCoordinateBuffer != CVKGlyphTextureAtlas.GetInstance().GetCoordinateBufferHandle() || + hGlyphCoordinateBufferView != CVKGlyphTextureAtlas.GetInstance().GetCoordinateBufferViewHandle()) { + if (descriptorSetsState != CVK_RESOURCE_NEEDS_REBUILD) { + descriptorSetsState = CVK_RESOURCE_NEEDS_UPDATE; + } + } + return (attributeVertexCount > 0 || summaryVertexCount > 0) && + (vertexUniformBufferState != CVK_RESOURCE_CLEAN || + geometryUniformBufferState != CVK_RESOURCE_CLEAN || + vertexBuffersState != CVK_RESOURCE_CLEAN || + commandBuffersState != CVK_RESOURCE_CLEAN || + descriptorSetsState != CVK_RESOURCE_CLEAN || + pipelinesState != CVK_RESOURCE_CLEAN); + } + + @Override + public int DisplayUpdate() { + int ret = VK_SUCCESS; + cvkVisualProcessor.VerifyInRenderThread(); + + try (MemoryStack stack = stackPush()) { + // Update vertex buffers + if (vertexBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + DestroyVertexBuffers(); + ret = CreateVertexBuffers(); + if (VkFailed(ret)) { return ret; } + } else if (vertexBuffersState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateVertexBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Vertex uniform buffer (camera guff) + if (vertexUniformBufferState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateVertexUniformBuffers(stack); + if (VkFailed(ret)) { return ret; } + } else if (vertexUniformBufferState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateVertexUniformBuffers(stack); + if (VkFailed(ret)) { return ret; } + } + + // Geometry uniform buffer (projection, highlight colour) + if (geometryUniformBufferState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateGeometryUniformBuffers(stack); + if (VkFailed(ret)) { return ret; } + } else if (geometryUniformBufferState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateGeometryUniformBuffers(stack); + if (VkFailed(ret)) { return ret; } + } + + // Descriptors (binding values to shaders parameters) + if (descriptorSetsState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateDescriptorSets(stack); + if (VkFailed(ret)) { return ret; } + } else if (descriptorSetsState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateDescriptorSets(stack); + if (VkFailed(ret)) { return ret; } + } + + // Command buffers (rendering commands enqueued on the GPU) + if (commandBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateCommandBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Pipelines (all the render state and resources in one object) + if (pipelinesState == CVK_RESOURCE_NEEDS_REBUILD) { + displayPipelines = new ArrayList<>(cvkSwapChain.GetImageCount()); + ret = CreatePipelines(cvkSwapChain.GetRenderPassHandle(), displayPipelines); + if (VkFailed(ret)) { return ret; } + + } + } + + return ret; + } + + + // ========================> Tasks <======================== \\ + + @Override + public void addGlyph(int glyphIndex, float x, float y, GlyphStreamContext streamContext) { + if (streamContext instanceof ConnectionGlyphStreamContext) { + final ConnectionGlyphStreamContext context = (ConnectionGlyphStreamContext) streamContext; + + if (context.isAttributeLabel) { + attributeVertices.add(new Vertex(currentWidth, + x, y, + context.visibility, + context.currentLowNodeId, + context.currentHighNodeId, + context.labelNumber, + context.totalScale, + context.currentOffset, + glyphIndex, + context.currentStagger, + context.currentLinkLabelCount)); + } else { + summaryVertices.add(new Vertex(currentWidth, + x, y, + context.visibility, + context.currentLowNodeId, + context.currentHighNodeId, + context.labelNumber, + context.totalScale, + context.currentOffset, + glyphIndex, + context.currentStagger, + context.currentLinkLabelCount)); + } + } else { + throw new IllegalArgumentException("Provided context lacks Connection information, please use a ConnectionGlyphStreamContext"); + } + } + + @Override + public void newLine(float width, GlyphStreamContext streamContext) { + currentWidth = -width / 2.0f - 0.2f; + + if (streamContext instanceof ConnectionGlyphStreamContext) { + final ConnectionGlyphStreamContext context = (ConnectionGlyphStreamContext) streamContext; + + if (context.isAttributeLabel) { + attributeVertices.add(new Vertex(currentWidth, + currentWidth, 0.0f, + context.visibility, + context.currentLowNodeId, + context.currentHighNodeId, + context.labelNumber, + context.totalScale, + context.currentOffset, + CVKGlyphTextureAtlas.BACKGROUND_GLYPH_INDEX, + context.currentStagger, + context.currentLinkLabelCount)); + } else { + summaryVertices.add(new Vertex(currentWidth, + currentWidth, 0.0f, + context.visibility, + context.currentLowNodeId, + context.currentHighNodeId, + context.labelNumber, + context.totalScale, + context.currentOffset, + CVKGlyphTextureAtlas.BACKGROUND_GLYPH_INDEX, + context.currentStagger, + context.currentLinkLabelCount)); + } + } else { + throw new IllegalArgumentException("Provided context lacks Connection information, please use a ConnectionGlyphStreamContext"); + } + } + + private void BufferLabel(final int pos, final int link, final VisualAccess access, final ConnectionGlyphStreamContext context) { + final int connection = access.getLinkConnection(link, pos); + final int width = (int) (LabelUtilities.NRADIUS_TO_LINE_WIDTH_UNITS * Math.min(LabelUtilities.MAX_TRANSACTION_WIDTH, access.getConnectionWidth(connection))); + context.isAttributeLabel = !access.getIsLabelSummary(connection); + + context.currentStagger = context.currentStagger == MAX_STAGGERS ? 1 : context.currentStagger + 1; + if (context.nextLeftOffset == 0) { + context.nextLeftOffset += ((width / 2) + LabelUtilities.NRADIUS_TO_LINE_WIDTH_UNITS); + context.nextRightOffset -= ((width / 2) + LabelUtilities.NRADIUS_TO_LINE_WIDTH_UNITS); + } else if (context.nextLeftOffset <= -context.nextRightOffset) { + context.currentOffset = context.nextLeftOffset + (width / 2); + context.nextLeftOffset += (width + LabelUtilities.NRADIUS_TO_LINE_WIDTH_UNITS); + } else { + context.currentOffset = context.nextRightOffset - (width / 2); + context.nextRightOffset -= (width + LabelUtilities.NRADIUS_TO_LINE_WIDTH_UNITS); + } + + context.totalScale = 0; + context.visibility = access.getConnectionVisibility(connection); + for (int label = 0; label < access.getConnectionLabelCount(connection); label++) { + context.labelNumber = label; + final String text = access.getConnectionLabelText(connection, label); + ArrayList lines = LabelUtilities.splitTextIntoLines(text); + for (final String line : lines) { + CVKGlyphTextureAtlas.GetInstance().RenderTextAsLigatures(line, this, context); + if (context.isAttributeLabel) { + context.totalScale += labelSizes.a[label]; + } else { + // Should this be cached to prevent thread contention? + context.totalScale += vertexUBO.summaryLabelInfo.get(label, 3); + } + } + } + } + + class BufferLabelWorker extends Thread { + private final int pos; + private final int link; + private final VisualAccess access; + private final ConnectionGlyphStreamContext context; + + BufferLabelWorker(final int pos, final int link, final VisualAccess access, final ConnectionGlyphStreamContext context) { + this.pos = pos; + this.link = link; + this.access = access; + this.context = context; + } + + @Override + public void run() { + BufferLabel(pos, link, access, context); + } + } + + private void FillLabels(final VisualAccess access) throws InterruptedException { + final ExecutorService pool = Executors.newFixedThreadPool(NUM_CORES); + final int numLinks = access.getLinkCount(); + for (int link = 0; link < numLinks; ++link) { + final ConnectionGlyphStreamContext context = new ConnectionGlyphStreamContext(); + context.currentLinkLabelCount = access.getLinkConnectionCount(link); + context.currentLowNodeId = access.getLinkLowVertex(link); + context.currentHighNodeId = access.getLinkHighVertex(link); + + final int numConnections = access.getLinkConnectionCount(link); + for (int pos = 0; pos < numConnections; pos++) { + final Runnable thread = new BufferLabelWorker(pos, link, access, context); + pool.submit(thread); + } + } + + pool.shutdown(); + + pool.awaitTermination(10, TimeUnit.MINUTES); + } + + class FillLabelsWorker extends Thread { + + private final VisualAccess access; + + FillLabelsWorker(final VisualAccess access) { + this.access = access; + } + + @Override + public void run() { + try { + FillLabels(access); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + GetLogger().LogException(ex, "Exception thrown from FillLabelsWorker"); + } + } + } + + private void BuildVertexArrays(final VisualAccess access) { + attributeVertices = new ArrayList<>(); + summaryVertices = new ArrayList<>(); + + if (access.getLinkCount() > 0) { + final Thread labelThread = new FillLabelsWorker(access); + labelThread.start(); + + try { + labelThread.join(); + } catch (InterruptedException ex) { + GetLogger().LogException(ex, "Exception thrown from BuildVertexArrays"); + } + } + + final int numLinks = access.getLinkCount(); + for (int link = 0; link < numLinks; ++link) { + final ConnectionGlyphStreamContext context = new ConnectionGlyphStreamContext(); + context.currentLinkLabelCount = access.getLinkConnectionCount(link); + context.currentLowNodeId = access.getLinkLowVertex(link); + context.currentHighNodeId = access.getLinkHighVertex(link); + + final int numConnections = access.getLinkConnectionCount(link); + for (int pos = 0; pos < numConnections; pos++) { + final int connection = access.getLinkConnection(link, pos); + final int width = (int) (LabelUtilities.NRADIUS_TO_LINE_WIDTH_UNITS * Math.min(LabelUtilities.MAX_TRANSACTION_WIDTH, access.getConnectionWidth(connection))); + context.isAttributeLabel = !access.getIsLabelSummary(connection); + + context.currentStagger = context.currentStagger == MAX_STAGGERS ? 1 : context.currentStagger + 1; + if (context.nextLeftOffset == 0) { + context.nextLeftOffset += ((width / 2) + LabelUtilities.NRADIUS_TO_LINE_WIDTH_UNITS); + context.nextRightOffset -= ((width / 2) + LabelUtilities.NRADIUS_TO_LINE_WIDTH_UNITS); + } else if (context.nextLeftOffset <= -context.nextRightOffset) { + context.currentOffset = context.nextLeftOffset + (width / 2); + context.nextLeftOffset += (width + LabelUtilities.NRADIUS_TO_LINE_WIDTH_UNITS); + } else { + context.currentOffset = context.nextRightOffset - (width / 2); + context.nextRightOffset -= (width + LabelUtilities.NRADIUS_TO_LINE_WIDTH_UNITS); + } + + context.totalScale = 0; + context.visibility = access.getConnectionVisibility(connection); + for (int label = 0; label < access.getConnectionLabelCount(connection); label++) { + context.labelNumber = label; + final String text = access.getConnectionLabelText(connection, label); + ArrayList lines = LabelUtilities.splitTextIntoLines(text); + for (final String line : lines) { + CVKGlyphTextureAtlas.GetInstance().RenderTextAsLigatures(line, this, context); + if (context.isAttributeLabel) { + context.totalScale += labelSizes.a[label]; + } else { + // Should this be cached to prevent thread contention? + context.totalScale += vertexUBO.summaryLabelInfo.get(label, 3); + } + } + } + } + } + } + + private void RebuildVertexStagingBuffers(Vertex[] attributeVertices, Vertex[] summaryVertices) { + attributeVertexCount = (attributeVertices != null ? attributeVertices.length : 0); + int newSizeBytes = attributeVertexCount * Vertex.BYTES; + boolean recreate = cvkAttributeVertexStagingBuffer == null || newSizeBytes != cvkAttributeVertexStagingBuffer.GetBufferSize(); + + if (recreate) { + if (cvkAttributeVertexStagingBuffer != null) { + cvkAttributeVertexStagingBuffer.Destroy(); + cvkAttributeVertexStagingBuffer = null; + } + + if (newSizeBytes > 0) { + cvkAttributeVertexStagingBuffer = CVKBuffer.Create(newSizeBytes, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKLinkLabelsRenderable cvkAttributeVertexStagingBuffer"); + } + } + + if (attributeVertices != null && newSizeBytes > 0) { + UpdateVertexStagingBuffer(cvkAttributeVertexStagingBuffer, attributeVertices, 0, attributeVertices.length - 1); + } + + summaryVertexCount = (summaryVertices != null ? summaryVertices.length : 0); + newSizeBytes = summaryVertexCount * Vertex.BYTES; + recreate = cvkSummaryVertexStagingBuffer == null || newSizeBytes != cvkSummaryVertexStagingBuffer.GetBufferSize(); + + if (recreate) { + if (cvkSummaryVertexStagingBuffer != null) { + cvkSummaryVertexStagingBuffer.Destroy(); + cvkSummaryVertexStagingBuffer = null; + } + + if (newSizeBytes > 0) { + cvkSummaryVertexStagingBuffer = CVKBuffer.Create(newSizeBytes, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKLinkLabelsRenderable cvkSummaryVertexStagingBuffer"); + } + } + + if (summaryVertices != null && newSizeBytes > 0) { + UpdateVertexStagingBuffer(cvkSummaryVertexStagingBuffer, summaryVertices, 0, summaryVertices.length - 1); + } + } + + private void UpdateVertexStagingBuffer(CVKBuffer stagingBuffer, Vertex[] vertices, int first, int last) { + CVKAssertNotNull(stagingBuffer); + CVKAssertNotNull(vertices != null); + CVKAssert(vertices.length > 0 && vertices.length > (last - first)); + CVKAssert(last >= 0 && last >= first && first >= 0); + + int offset = first * Vertex.BYTES; + int size = ((last - first) + 1) * Vertex.BYTES; + + ByteBuffer pMemory = stagingBuffer.StartMemoryMap(offset, size); + for (Vertex vertex : vertices) { + vertex.CopyToSequentially(pMemory); + } + stagingBuffer.EndMemoryMap(); + pMemory = null; // now unmapped, do not use + } + + public CVKRenderUpdateTask TaskUpdateLabels(final VisualChange change, final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + cvkVisualProcessor.GetLogger().fine("TaskUpdateLabels frame %d: %d links", cvkVisualProcessor.GetFrameNumber(), access.getLinkCount()); + + // We receive a single VisualChange but need to update a vertex array + // for attribute labels and summary labels. As there is no way to know + // just from the change what range to use for each we must fully rebuild + // each time this method is called. + BuildVertexArrays(access); + + // Copy to arrays which we can be accessed by the render thread. The + // actual vertix lists should only ever be touched by the VisualProcessor. + Vertex[] attributeVerticesCopy = new Vertex[attributeVertices.size()]; + attributeVertices.toArray(attributeVerticesCopy); + Vertex[] summaryVerticesCopy = new Vertex[summaryVertices.size()]; + summaryVertices.toArray(summaryVerticesCopy); + + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + RebuildVertexStagingBuffers(attributeVerticesCopy, summaryVerticesCopy); + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + }; + } + + public CVKRenderUpdateTask TaskUpdateColours(final VisualChange change, final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + final int numConnectionLabels = Math.min(LabelUtilities.MAX_LABELS_TO_DRAW, access.getConnectionAttributeLabelCount()); + cvkVisualProcessor.GetLogger().fine("TaskUpdateColours frame %d: %d connection attribute labels", cvkVisualProcessor.GetFrameNumber(), numConnectionLabels); + + for (int i = 0; i < numConnectionLabels; i++) { + final ConstellationColor labelColour = access.getConnectionLabelColor(i); + labelColours[i] = new Vector3f(labelColour.getRed(), labelColour.getGreen(), labelColour.getBlue()); + } + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + for (int i = 0; i < numConnectionLabels; i++) { + vertexUBO.attributeLabelInfo.setRow(labelColours[i], i); + } + if (vertexUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetVertexUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + public CVKRenderUpdateTask TaskUpdateSizes(final VisualChange change, final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + final int numConnectionLabels = Math.min(LabelUtilities.MAX_LABELS_TO_DRAW, access.getConnectionAttributeLabelCount()); + cvkVisualProcessor.GetLogger().fine("TaskUpdateSizes frame %d: %d connection attribute labels", cvkVisualProcessor.GetFrameNumber(), numConnectionLabels); + + for (int i = 0; i < numConnectionLabels; i++) { + labelSizes.a[i] = (int)(LabelUtilities.NRADIUS_TO_LABEL_UNITS * Math.min(access.getConnectionLabelSize(i), LabelUtilities.MAX_LABEL_SIZE)); + } + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + for (int i = 0; i < numConnectionLabels; i++) { + vertexUBO.attributeLabelInfo.set(i, 3, labelSizes.a[i]); + } + if (vertexUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetVertexUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + public CVKRenderUpdateTask TaskSetBackgroundColor(final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + cvkVisualProcessor.GetLogger().fine("TaskSetBackgroundColor frame %d: %d verts", cvkVisualProcessor.GetFrameNumber(), access.getVertexCount()); + final ConstellationColor colour = access.getBackgroundColor(); + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + vertexUBO.backgroundColor.set(colour.getRed(), colour.getGreen(), colour.getBlue(), 1.0f); + if (vertexUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetVertexUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + public CVKRenderUpdateTask TaskSetHighlightColor(final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + cvkVisualProcessor.GetLogger().fine("TaskSetHighlightColor frame %d: %d verts", cvkVisualProcessor.GetFrameNumber(), access.getVertexCount()); + final ConstellationColor colour = access.getHighlightColor(); + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + geometryUBO.highlightColor.set(colour.getRed(), colour.getGreen(), colour.getBlue(), 1.0f); + if (geometryUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + public CVKRenderUpdateTask TaskUpdateCamera() { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + UpdatePushConstants(); + }; + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKLinksRenderable.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKLinksRenderable.java new file mode 100644 index 0000000000..bdf5cb329f --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKLinksRenderable.java @@ -0,0 +1,1494 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.renderables; + +import au.gov.asd.tac.constellation.utilities.color.ConstellationColor; +import au.gov.asd.tac.constellation.utilities.graphics.Matrix44f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector4f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector4i; +import au.gov.asd.tac.constellation.utilities.text.LabelUtilities; +import au.gov.asd.tac.constellation.utilities.visual.VisualAccess; +import au.gov.asd.tac.constellation.utilities.visual.VisualAccess.ConnectionDirection; +import au.gov.asd.tac.constellation.utilities.visual.VisualChange; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool.CVKDescriptorPoolRequirements; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import au.gov.asd.tac.constellation.visual.vulkan.CVKRenderUpdateTask; +import au.gov.asd.tac.constellation.visual.vulkan.CVKSwapChain; +import au.gov.asd.tac.constellation.visual.vulkan.CVKVisualProcessor; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_CLEAN; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_NEEDS_REBUILD; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_NEEDS_UPDATE; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKBuffer; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKCommandBuffer; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssert; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssertNotNull; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssertNull; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.IDENTITY_44F; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.PutMatrix44f; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkFailed; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkSucceeded; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.checkVKret; +import java.nio.ByteBuffer; +import java.nio.LongBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; +import org.lwjgl.system.MemoryStack; +import static org.lwjgl.system.MemoryStack.stackPush; +import org.lwjgl.system.MemoryUtil; +import static org.lwjgl.system.MemoryUtil.memFree; +import static org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_TRANSFER_DST_BIT; +import static org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_TRANSFER_SRC_BIT; +import static org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; +import static org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_LEVEL_SECONDARY; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT; +import static org.lwjgl.vulkan.VK10.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +import static org.lwjgl.vulkan.VK10.VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32B32A32_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32B32A32_SINT; +import static org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; +import static org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; +import static org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.VK_PRIMITIVE_TOPOLOGY_LINE_LIST; +import static org.lwjgl.vulkan.VK10.VK_SHADER_STAGE_GEOMETRY_BIT; +import static org.lwjgl.vulkan.VK10.VK_SHADER_STAGE_VERTEX_BIT; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; +import static org.lwjgl.vulkan.VK10.VK_SUCCESS; +import static org.lwjgl.vulkan.VK10.VK_VERTEX_INPUT_RATE_VERTEX; +import static org.lwjgl.vulkan.VK10.vkAllocateDescriptorSets; +import static org.lwjgl.vulkan.VK10.vkCreateDescriptorSetLayout; +import static org.lwjgl.vulkan.VK10.vkCreatePipelineLayout; +import static org.lwjgl.vulkan.VK10.vkDestroyDescriptorSetLayout; +import static org.lwjgl.vulkan.VK10.vkDestroyPipelineLayout; +import static org.lwjgl.vulkan.VK10.vkFreeDescriptorSets; +import static org.lwjgl.vulkan.VK10.vkUpdateDescriptorSets; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkCommandBufferInheritanceInfo; +import org.lwjgl.vulkan.VkDescriptorBufferInfo; +import org.lwjgl.vulkan.VkDescriptorSetAllocateInfo; +import org.lwjgl.vulkan.VkDescriptorSetLayoutBinding; +import org.lwjgl.vulkan.VkDescriptorSetLayoutCreateInfo; +import org.lwjgl.vulkan.VkPipelineLayoutCreateInfo; +import org.lwjgl.vulkan.VkPushConstantRange; +import org.lwjgl.vulkan.VkVertexInputAttributeDescription; +import org.lwjgl.vulkan.VkVertexInputBindingDescription; +import org.lwjgl.vulkan.VkWriteDescriptorSet; + + +/******************************************************************************* + * CVKLinksRenderable + * + * This class renders links between nodes as lines. It is paired with CVKPerspectiveLinksRenderable + * which renders links between nodes with triangles in order to achieve perspective + * and to add direction arrows. + * + * These bonded pair of classes fulfill the same role as au.gov.asd.tac.constellation. + * visual.opengl.renderer.batcher.LineBatcher in the JOGL version. + * + * LineBatcher draws both a line and triangle-line between two nodes. When the + * camera is close only the triangle-line is visible. As the camera pans out + * the triangle-line becomes less visible eventually becoming sub-pixel. The + * line which was previously not visible becomes visible. It's surprisingly + * effective. + * + * The reason this was split into two renderables in the Vulkan display project + * is that CVKRenderables encapsulate a single pipeline which have a single shader + * for each assembly step (vert, geo, frag). Subsequently they only output a single + * primitive type (triangle_strip, line_list, point_list etc). So rather than + * introduce the complexity of multiple display pipelines into the rest of the + * renderables we just share data and have a renderable for each pipeline. + *******************************************************************************/ + +public class CVKLinksRenderable extends CVKRenderable { + // Resources recreated with the swap chain (dependent on the image count) + private LongBuffer pDescriptorSets = null; + private List hitTestPipelines = null; + private List displayCommandBuffers = null; + private List hittestCommandBuffers = null; + private List vertexBuffers = null; + private List vertexUniformBuffers = null; + private List geometryUniformBuffers = null; + + // The UBO staging buffers are a known size so created outside user events + private CVKBuffer cvkVertexUBStagingBuffer = null; + private CVKBuffer cvkGeometryUBStagingBuffer = null; + private final VertexUniformBufferObject vertexUBO = new VertexUniformBufferObject(); + private final GeometryUniformBufferObject geometryUBO = new GeometryUniformBufferObject(); + + // Resources recreated only through user events + private int vertexCount = 0; + private CVKBuffer cvkVertexStagingBuffer = null; + + // Resources we don't own but use and must track so we know when to update + // our descriptors + private long hPositionBuffer = VK_NULL_HANDLE; + private long hPositionBufferView = VK_NULL_HANDLE; + + // Push constants for shaders contains the MV matrix and drawHitTest int + private ByteBuffer modelViewPushConstants = null; + private ByteBuffer hitTestPushConstants = null; + private static final int MODEL_VIEW_PUSH_CONSTANT_STAGES = VK_SHADER_STAGE_VERTEX_BIT; + private static final int HIT_TEST_PUSH_CONSTANT_STAGES = VK_SHADER_STAGE_GEOMETRY_BIT; + private static final int MODEL_VIEW_PUSH_CONSTANT_SIZE = Matrix44f.BYTES; + private static final int HIT_TEST_PUSH_CONSTANT_SIZE = Integer.BYTES; + + private static final int LINE_INFO_ARROW = 1; + private static final int LINE_INFO_BITS_AVOID = 4; + private static final int FLOAT_MULTIPLIER = 1024; + private static final int NEW_LINK = -345; + + private float leftOffset; + private float rightOffset; + private Vector4f highlightColour = new Vector4f(); + private float opacity; + + + // ========================> Classes <======================== \\ + + protected static class Vertex { + // This looks a little weird for Java, but LWJGL and JOGL both require + // contiguous memory which is passed to the native GL or VK libraries. + private static final int BYTES = Vector4f.BYTES + Vector4i.BYTES; + private static final int OFFSETOF_DATA = Vector4f.BYTES; + private static final int OFFSET_CLR = 0; + private static final int BINDING = 0; + private Vector4f colour = new Vector4f(); + private Vector4i data = new Vector4i(); + + private Vertex() {} + + /** + * + * @param inColour + * @param visibility: alpha + * @param transactionID: transaction ID used for hit test pick colour + * @param encodedNodeIndex: encoded node index (used to lookup the positions texel buffer) + * @param flags: encoded selected and dimmed flags + * @param encodedWidthAndStyle: encoded width and line style (referenced via iVec4.q in the shaders) + */ + public Vertex(ConstellationColor inColour, + float visibility, + int transactionID, + int encodedNodeIndex, + int flags, + int encodedWidthAndStyle) { + colour.a[0] = inColour.getRed(); + colour.a[1] = inColour.getGreen(); + colour.a[2] = inColour.getBlue(); + colour.a[3] = visibility; + data.a[0] = transactionID; + data.a[1] = encodedNodeIndex; + data.a[2] = flags; + data.a[3] = encodedWidthAndStyle; + } + + + + public void CopyToSequentially(ByteBuffer buffer) { + buffer.putFloat(colour.a[0]); + buffer.putFloat(colour.a[1]); + buffer.putFloat(colour.a[2]); + buffer.putFloat(colour.a[3]); + buffer.putInt(data.a[0]); + buffer.putInt(data.a[1]); + buffer.putInt(data.a[2]); + buffer.putInt(data.a[3]); + } + + /** + * A VkVertexInputBindingDescription defines the rate at which data is + * consumed by vertex shader (per vertex or per instance). + * The input rate determines whether to move to the next data entry after + * each vertex or after each instance. + * The binding description also defines the vertex stride, the number of + * bytes that must be stepped from vertex n-1 to vertex n. + * + * @return Binding description for the FPS vertex type + */ + protected static VkVertexInputBindingDescription.Buffer GetBindingDescription() { + + VkVertexInputBindingDescription.Buffer bindingDescription = + VkVertexInputBindingDescription.callocStack(1); + + // If we bind multiple vertex buffers with different descriptions + // this is the index of this description occupies in the array of + // bound descriptions. + bindingDescription.binding(BINDING); + bindingDescription.stride(Vertex.BYTES); + bindingDescription.inputRate(VK_VERTEX_INPUT_RATE_VERTEX); + + return bindingDescription; + } + + /** + * A VkVertexInputAttributeDescription describes each element int the + * vertex buffer. + * binding: matches the binding member of VkVertexInputBindingDescription + * location: corresponds to the layout(location = #) in the vertex shader + * for this element (0 for data, 1 for bkgClr). + * format: format the shader will interpret this as. + * offset: bytes from the start of the vertex this attribute starts at + * + * @return + */ + protected static VkVertexInputAttributeDescription.Buffer GetAttributeDescriptions() { + + VkVertexInputAttributeDescription.Buffer attributeDescriptions = + VkVertexInputAttributeDescription.callocStack(2); + + // backgroundIconColor + VkVertexInputAttributeDescription posDescription = attributeDescriptions.get(0); + posDescription.binding(BINDING); + posDescription.location(0); + posDescription.format(VK_FORMAT_R32G32B32A32_SFLOAT); + posDescription.offset(OFFSET_CLR); + + // data + VkVertexInputAttributeDescription colorDescription = attributeDescriptions.get(1); + colorDescription.binding(BINDING); + colorDescription.location(1); + colorDescription.format(VK_FORMAT_R32G32B32A32_SINT); + colorDescription.offset(OFFSETOF_DATA); + + return attributeDescriptions.rewind(); + } + } + + @Override + protected VkVertexInputBindingDescription.Buffer GetVertexBindingDescription() { + return Vertex.GetBindingDescription(); + } + + @Override + protected VkVertexInputAttributeDescription.Buffer GetVertexAttributeDescriptions() { + return Vertex.GetAttributeDescriptions(); + } + + protected static class VertexUniformBufferObject { + public float morphMix = 0; + private static Integer padding = null; + + protected static int SizeOf() { + if (padding == null) { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + final int minAlignment = CVKDevice.GetMinUniformBufferAlignment(); + + // The matrices are 64 bytes each so should line up on a boundary (unless the minimum alignment is huge) + CVKAssert(minAlignment <= (16 * Float.BYTES)); + + int sizeof = 1 * Float.BYTES; // morphMix + final int overrun = sizeof % minAlignment; + padding = overrun > 0 ? minAlignment - overrun : 0; + } + + return 1 * Float.BYTES + // morphMix + padding; + } + + private void CopyTo(ByteBuffer buffer) { + buffer.putFloat(morphMix); + + for (int i = 0; i < padding; ++i) { + buffer.put((byte)0); + } + } + } + + protected static class GeometryUniformBufferObject { + private final Matrix44f pMatrix = new Matrix44f(); + private final Vector4f highlightColour = new Vector4f(); + private float visibilityLow; + private float visibilityHigh; + private float directionMotion; + private float alpha; + private static Integer padding = null; + + protected static int SizeOf() { + if (padding == null) { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + final int minAlignment = CVKDevice.GetMinUniformBufferAlignment(); + + // The matrices are 64 bytes each so should line up on a boundary (unless the minimum alignment is huge) + CVKAssert(minAlignment <= (Matrix44f.BYTES)); + + int sizeof = Matrix44f.BYTES + // pMatrix + Vector4f.BYTES + // highlightColour + 1 * Float.BYTES + // visibilityLow + 1 * Float.BYTES + // visibilityHigh + 1 * Float.BYTES + // directionMotion + 1 * Float.BYTES; // alpha + + final int overrun = sizeof % minAlignment; + padding = overrun > 0 ? minAlignment - overrun : 0; + } + + return Matrix44f.BYTES + // pMatrix + Vector4f.BYTES + // highlightColour + 1 * Float.BYTES + // visibilityLow + 1 * Float.BYTES + // visibilityHigh + 1 * Float.BYTES + // directionMotion + 1 * Float.BYTES + // alpha + padding; + } + + private void CopyTo(ByteBuffer buffer) { + PutMatrix44f(buffer, pMatrix); + for (int i = 0; i < 4; ++i) { + buffer.putFloat(highlightColour.a[i]); + } + buffer.putFloat(visibilityLow); + buffer.putFloat(visibilityHigh); + buffer.putFloat(directionMotion); + buffer.putFloat(alpha); + + for (int i = 0; i < padding; ++i) { + buffer.put((byte)0); + } + } + } + + + // ========================> Shaders <======================== \\ + + @Override + protected String GetVertexShaderName() { return "Line.vs"; } + + @Override + protected String GetGeometryShaderName() { return "LineLine.gs"; } + + @Override + protected String GetFragmentShaderName() { return "LineLine.fs"; } + + + // ========================> Lifetime <======================== \\ + + public CVKLinksRenderable(CVKVisualProcessor visualProcessor) { + super(visualProcessor); + + //points? + assemblyTopology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + } + + private void CreateUBOStagingBuffers() { + cvkVertexUBStagingBuffer = CVKBuffer.Create(VertexUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKLinksRenderable.CreateUBOStagingBuffers cvkVertexUBStagingBuffer"); + cvkGeometryUBStagingBuffer = CVKBuffer.Create(GeometryUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKLinksRenderable.CreateUBOStagingBuffers cvkGeometryUBStagingBuffer"); + } + + @Override + public int Initialise() { + int ret = super.Initialise(); + if (VkFailed(ret)) { return ret; } + + // Check for double initialisation + CVKAssert(hDescriptorLayout == VK_NULL_HANDLE); + + CreatePushConstants(); + + ret = CreateDescriptorLayout(); + if (VkFailed(ret)) { return ret; } + + ret = CreatePipelineLayout(); + if (VkFailed(ret)) { return ret; } + + CreateUBOStagingBuffers(); + + return ret; + } + + private void DestroyStagingBuffers() { + if (cvkVertexStagingBuffer != null) { + cvkVertexStagingBuffer.Destroy(); + cvkVertexStagingBuffer = null; + } + if (cvkVertexUBStagingBuffer != null) { + cvkVertexUBStagingBuffer.Destroy(); + cvkVertexUBStagingBuffer = null; + } + if (cvkGeometryUBStagingBuffer != null) { + cvkGeometryUBStagingBuffer.Destroy(); + cvkGeometryUBStagingBuffer = null; + } + } + + @Override + public void Destroy() { + DestroyVertexBuffers(); + DestroyVertexUniformBuffers(); + DestroyGeometryUniformBuffers(); + DestroyDescriptorSets(); + DestroyDescriptorLayout(); + DestroyPipelines(); + DestroyPipelineLayout(); + DestroyCommandBuffers(); + DestroyStagingBuffers(); + DestroyPushConstants(); + + CVKAssertNull(vertexBuffers); + CVKAssertNull(vertexUniformBuffers); + CVKAssertNull(geometryUniformBuffers); + CVKAssertNull(pDescriptorSets); + CVKAssertNull(hDescriptorLayout); + CVKAssertNull(displayCommandBuffers); + CVKAssertNull(displayPipelines); + CVKAssertNull(hPipelineLayout); + CVKAssertNull(modelViewPushConstants); + CVKAssertNull(hitTestPushConstants); + } + + + // ========================> Swap chain <======================== \\ + + @Override + protected int DestroySwapChainResources() { + this.cvkSwapChain = null; + + // We only need to recreate these resources if the number of images in + // the swapchain changes or if this is the first call after the initial + // swapchain is created. + if (displayPipelines != null && swapChainImageCountChanged) { + DestroyVertexBuffers(); + DestroyVertexUniformBuffers(); + DestroyGeometryUniformBuffers(); + DestroyDescriptorSets(); + DestroyCommandBuffers(); + DestroyPipelines(); + } + + return VK_SUCCESS; + } + + @Override + public int SetNewSwapChain(CVKSwapChain newSwapChain) { + int ret = super.SetNewSwapChain(newSwapChain); + if (VkFailed(ret)) { return ret; } + + if (swapChainImageCountChanged) { + // The number of images has changed, we need to rebuild all image + // buffered resources + SetVertexUniformBufferState(CVK_RESOURCE_NEEDS_REBUILD); + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_REBUILD); + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + SetCommandBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + SetDescriptorSetsState(CVK_RESOURCE_NEEDS_REBUILD); + SetPipelinesState(CVK_RESOURCE_NEEDS_REBUILD); + } else { + // View frustum and projection matrix likely have changed. We don't + // need to rebuild our displayPipelines as the frustum is set by dynamic + // state in RecordDisplayCommandBuffer + if (geometryUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + } + + return ret; + } + + + // ========================> Vertex buffers <======================== \\ + + private int CreateVertexBuffers() { + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + + // We can only create vertex buffers if we have something to put in them + if (cvkVertexStagingBuffer.GetBufferSize() > 0) { + int imageCount = cvkSwapChain.GetImageCount(); + vertexBuffers = new ArrayList<>(); + + for (int i = 0; i < imageCount; ++i) { + CVKBuffer cvkVertexBuffer = CVKBuffer.Create(cvkVertexStagingBuffer.GetBufferSize(), + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKLinksRenderable cvkVertexBuffer %d", i)); + vertexBuffers.add(cvkVertexBuffer); + } + + // Populate them with some values + return UpdateVertexBuffers(); + } + + return ret; + } + + private int UpdateVertexBuffers() { + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssert(cvkVertexStagingBuffer != null); + CVKAssert(vertexBuffers != null); + CVKAssert(vertexBuffers.size() > 0); + CVKAssert(cvkVertexStagingBuffer.GetBufferSize() == vertexBuffers.get(0).GetBufferSize()); + int ret = VK_SUCCESS; + +// List DEBUG_vertexDescriptors = new ArrayList<>(); +// DEBUG_vertexDescriptors.add(new DEBUG_CVKBufferElementDescriptor("r", Float.TYPE)); +// DEBUG_vertexDescriptors.add(new DEBUG_CVKBufferElementDescriptor("g", Float.TYPE)); +// DEBUG_vertexDescriptors.add(new DEBUG_CVKBufferElementDescriptor("b", Float.TYPE)); +// DEBUG_vertexDescriptors.add(new DEBUG_CVKBufferElementDescriptor("a", Float.TYPE)); +// DEBUG_vertexDescriptors.add(new DEBUG_CVKBufferElementDescriptor("mainIconIndices", Integer.TYPE)); +// DEBUG_vertexDescriptors.add(new DEBUG_CVKBufferElementDescriptor("decoratorWestIconIndices", Integer.TYPE)); +// DEBUG_vertexDescriptors.add(new DEBUG_CVKBufferElementDescriptor("decoratorEastIconIndices", Integer.TYPE)); +// DEBUG_vertexDescriptors.add(new DEBUG_CVKBufferElementDescriptor("vertexIndex", Integer.TYPE)); +// cvkVertexStagingBuffer.DEBUGPRINT(DEBUG_vertexDescriptors); + + for (int i = 0; i < vertexBuffers.size(); ++i) { + CVKBuffer cvkVertexBuffer = vertexBuffers.get(i); + ret = cvkVertexBuffer.CopyFrom(cvkVertexStagingBuffer); + if (VkFailed(ret)) { return ret; } + } + + // Note the staging buffer is not freed as we can simplify the update tasks + // by just updating it and then copying it over again during ProcessRenderTasks(). + SetVertexBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public int GetVertexCount() { return cvkVisualProcessor.GetDrawFlags().drawConnections() ? vertexCount : 0; } + + private void DestroyVertexBuffers() { + if (vertexBuffers != null) { + vertexBuffers.forEach(el -> {el.Destroy();}); + vertexBuffers.clear(); + vertexBuffers = null; + } + } + + + // ========================> Uniform buffers <======================== \\ + + private int CreateVertexUniformBuffers() { + CVKAssertNotNull(cvkSwapChain); + CVKAssert(vertexUniformBuffers == null); + + vertexUniformBuffers = new ArrayList<>(); + for (int i = 0; i < cvkSwapChain.GetImageCount(); ++i) { + CVKBuffer vertexUniformBuffer = CVKBuffer.Create(VertexUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKLinksRenderable vertexUniformBuffer %d", i)); + vertexUniformBuffers.add(vertexUniformBuffer); + } + return UpdateVertexUniformBuffers(); + } + + private int UpdateVertexUniformBuffers() { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkVertexUBStagingBuffer); + CVKAssertNotNull(vertexUniformBuffers); + CVKAssert(vertexUniformBuffers.size() > 0); + + int ret = VK_SUCCESS; + + // Populate the UBO. This is easy to deal with, but not super efficient + // as we are effectively staging into the staging buffer below. + vertexUBO.morphMix = cvkVisualProcessor.getDisplayCamera().getMix(); + + // Staging buffer so our VBO can be device local (most performant memory) + ByteBuffer pMemory = cvkVertexUBStagingBuffer.StartMemoryMap(0, VertexUniformBufferObject.SizeOf()); + { + vertexUBO.CopyTo(pMemory); + } + cvkVertexUBStagingBuffer.EndMemoryMap(); + pMemory = null; + + // Copy the staging buffer into the uniform buffer on the device + final int imageCount = cvkSwapChain.GetImageCount(); + for (int i = 0; i < imageCount; ++i) { + ret = vertexUniformBuffers.get(i).CopyFrom(cvkVertexUBStagingBuffer); + if (VkFailed(ret)) { return ret; } + } + + UpdateVertexPushConstants(); + + // We are done, reset the resource state + SetVertexUniformBufferState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private void DestroyVertexUniformBuffers() { + if (vertexUniformBuffers != null) { + vertexUniformBuffers.forEach(el -> {el.Destroy();}); + vertexUniformBuffers = null; + } + } + + private int CreateGeometryUniformBuffers() { + CVKAssertNotNull(cvkSwapChain); + CVKAssert(geometryUniformBuffers == null); + + geometryUniformBuffers = new ArrayList<>(); + for (int i = 0; i < cvkSwapChain.GetImageCount(); ++i) { + CVKBuffer geometryUniformBuffer = CVKBuffer.Create(GeometryUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKLinksRenderable geometryUniformBuffer %d", i)); + geometryUniformBuffers.add(geometryUniformBuffer); + } + return UpdateGeometryUniformBuffers(); + } + + private int UpdateGeometryUniformBuffers() { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkGeometryUBStagingBuffer); + CVKAssertNotNull(geometryUniformBuffers); + CVKAssert(geometryUniformBuffers.size() > 0); + + int ret = VK_SUCCESS; + + // Populate the UBO. This is easy to deal with, but not super efficient + // as we are effectively staging into the staging buffer below. + geometryUBO.pMatrix.set(cvkVisualProcessor.GetProjectionMatrix()); + geometryUBO.highlightColour.set(highlightColour); + geometryUBO.visibilityLow = cvkVisualProcessor.getDisplayCamera().getVisibilityLow(); + geometryUBO.visibilityHigh = cvkVisualProcessor.getDisplayCamera().getVisibilityHigh(); + geometryUBO.directionMotion = cvkVisualProcessor.GetMotion(); + geometryUBO.alpha = opacity; + + // Staging buffer so our VBO can be device local (most performant memory) + ByteBuffer pMemory = cvkGeometryUBStagingBuffer.StartMemoryMap(0, GeometryUniformBufferObject.SizeOf()); + { + geometryUBO.CopyTo(pMemory); + } + cvkGeometryUBStagingBuffer.EndMemoryMap(); + pMemory = null; + + // Copy the staging buffer into the uniform buffer on the device + final int imageCount = cvkSwapChain.GetImageCount(); + for (int i = 0; i < imageCount; ++i) { + ret = geometryUniformBuffers.get(i).CopyFrom(cvkGeometryUBStagingBuffer); + if (VkFailed(ret)) { return ret; } + } + + // We are done, reset the resource state + SetGeometryUniformBufferState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private void DestroyGeometryUniformBuffers() { + if (geometryUniformBuffers != null) { + geometryUniformBuffers.forEach(el -> {el.Destroy();}); + geometryUniformBuffers = null; + } + } + + // ========================> Push constants <======================== \\ + + private int CreatePushConstants() { + // Initialise push constants to identity mtx + modelViewPushConstants = MemoryUtil.memAlloc(MODEL_VIEW_PUSH_CONSTANT_SIZE); + PutMatrix44f(modelViewPushConstants, IDENTITY_44F); + + // Set DrawHitTest to false + hitTestPushConstants = MemoryUtil.memAlloc(HIT_TEST_PUSH_CONSTANT_SIZE); + hitTestPushConstants.putInt(0); + + modelViewPushConstants.flip(); + hitTestPushConstants.flip(); + + return VK_SUCCESS; + } + + private void UpdateVertexPushConstants(){ + CVKAssertNotNull(cvkSwapChain); + + modelViewPushConstants.clear(); + PutMatrix44f(modelViewPushConstants, cvkVisualProcessor.getDisplayModelViewMatrix()); + modelViewPushConstants.flip(); + } + + protected void UpdatePushConstantsHitTest(boolean drawHitTest){ + CVKAssertNotNull(cvkSwapChain); + + hitTestPushConstants.clear(); + + if (drawHitTest) { + hitTestPushConstants.putInt(1); + } else { + hitTestPushConstants.putInt(0); + } + + hitTestPushConstants.flip(); + } + + private void DestroyPushConstants() { + if (modelViewPushConstants != null) { + memFree(modelViewPushConstants); + modelViewPushConstants = null; + } + + if (hitTestPushConstants != null) { + memFree(hitTestPushConstants); + hitTestPushConstants = null; + } + } + + + // ========================> Command buffers <======================== \\ + + public int CreateCommandBuffers(){ + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + int imageCount = cvkSwapChain.GetImageCount(); + + displayCommandBuffers = new ArrayList<>(imageCount); + hittestCommandBuffers = new ArrayList<>(imageCount); + + for (int i = 0; i < imageCount; ++i) { + CVKCommandBuffer buffer = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_SECONDARY, GetLogger(), String.format("CVKLinksRenderable %d", i)); + displayCommandBuffers.add(buffer); + + CVKCommandBuffer offscreenBuffer = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_SECONDARY, GetLogger(), String.format("CVKLinksRenderable Offscreen Buffer %d", i)); + hittestCommandBuffers.add(offscreenBuffer); + } + + SetCommandBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public VkCommandBuffer GetDisplayCommandBuffer(int imageIndex) { + return displayCommandBuffers.get(imageIndex).GetVKCommandBuffer(); + } + + @Override + public VkCommandBuffer GetHitTestCommandBuffer(int imageIndex) { + return hittestCommandBuffers.get(imageIndex).GetVKCommandBuffer(); + } + + @Override + public int RecordDisplayCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int imageIndex){ + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(CVKDevice.GetCommandPoolHandle()); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + CVKCommandBuffer commandBuffer = displayCommandBuffers.get(imageIndex); + CVKAssert(commandBuffer != null); + CVKAssert(displayPipelines.get(imageIndex) != null); + + commandBuffer.BeginRecordSecondary(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, inheritanceInfo); + + commandBuffer.SetViewPort(cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight()); + commandBuffer.SetScissor(cvkVisualProcessor.GetCanvas().GetCurrentSurfaceExtent()); + + commandBuffer.BindGraphicsPipeline(displayPipelines.get(imageIndex)); + commandBuffer.BindVertexInput(vertexBuffers.get(imageIndex).GetBufferHandle()); + + // Push MV matrix to the vertex shader + commandBuffer.PushConstants(hPipelineLayout, MODEL_VIEW_PUSH_CONSTANT_STAGES, 0, modelViewPushConstants); + + // Push drawHitTest flag to the geometry shader + commandBuffer.PushConstants(hPipelineLayout, HIT_TEST_PUSH_CONSTANT_STAGES, Matrix44f.BYTES, hitTestPushConstants); + + commandBuffer.BindGraphicsDescriptorSets(hPipelineLayout, pDescriptorSets.get(imageIndex)); + + commandBuffer.Draw(GetVertexCount()); + + ret = commandBuffer.FinishRecord(); + if (VkFailed(ret)) { return ret; } + + return ret; + } + + @Override + public int RecordHitTestCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int imageIndex){ + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssert(CVKDevice.GetCommandPoolHandle() != VK_NULL_HANDLE); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + // Set the hit test flag in the shaders to true + UpdatePushConstantsHitTest(true); + + CVKCommandBuffer commandBuffer = hittestCommandBuffers.get(imageIndex); + CVKAssertNotNull(commandBuffer); + CVKAssertNotNull(hitTestPipelines.get(imageIndex)); + + ret = commandBuffer.BeginRecordSecondary(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, + inheritanceInfo); + if (VkFailed(ret)) { return ret; } + + commandBuffer.SetViewPort(cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight()); + commandBuffer.SetScissor(cvkVisualProcessor.GetCanvas().GetCurrentSurfaceExtent()); + + commandBuffer.BindGraphicsPipeline(hitTestPipelines.get(imageIndex)); + commandBuffer.BindVertexInput(vertexBuffers.get(imageIndex).GetBufferHandle()); + + // Push MV matrix to the vertex shader + commandBuffer.PushConstants(hPipelineLayout, + MODEL_VIEW_PUSH_CONSTANT_STAGES, + 0, + modelViewPushConstants); + + // Push drawHitTest flag to the geometry shader + commandBuffer.PushConstants(hPipelineLayout, + HIT_TEST_PUSH_CONSTANT_STAGES, + MODEL_VIEW_PUSH_CONSTANT_SIZE, + hitTestPushConstants); + + commandBuffer.BindGraphicsDescriptorSets(hPipelineLayout, pDescriptorSets.get(imageIndex)); + + commandBuffer.Draw(GetVertexCount()); + + ret = commandBuffer.FinishRecord(); + if (VkFailed(ret)) { return ret; } + + // Reset hit test flag to false + UpdatePushConstantsHitTest(false); + + return ret; + } + + private void DestroyCommandBuffers() { + if (null != displayCommandBuffers) { + displayCommandBuffers.forEach(el -> {el.Destroy();}); + displayCommandBuffers.clear(); + displayCommandBuffers = null; + } + + if (null != hittestCommandBuffers) { + hittestCommandBuffers.forEach(el -> {el.Destroy();}); + hittestCommandBuffers.clear(); + hittestCommandBuffers = null; + } + } + + + // ========================> Descriptors <======================== \\ + + private int CreateDescriptorLayout() { + int ret; + + try (MemoryStack stack = stackPush()) { + VkDescriptorSetLayoutBinding.Buffer bindings = VkDescriptorSetLayoutBinding.callocStack(3, stack); + + // 0: Vertex uniform buffer + VkDescriptorSetLayoutBinding vertexUBDSLB = bindings.get(0); + vertexUBDSLB.binding(0); + vertexUBDSLB.descriptorCount(1); + vertexUBDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + vertexUBDSLB.pImmutableSamplers(null); + vertexUBDSLB.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + + // 1: Vertex samplerBuffer (position buffer) + VkDescriptorSetLayoutBinding vertexSamplerDSLB = bindings.get(1); + vertexSamplerDSLB.binding(1); + vertexSamplerDSLB.descriptorCount(1); + vertexSamplerDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + vertexSamplerDSLB.pImmutableSamplers(null); + vertexSamplerDSLB.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + + // 2: Geometry uniform buffer + VkDescriptorSetLayoutBinding geometryUBDSLB = bindings.get(2); + geometryUBDSLB.binding(2); + geometryUBDSLB.descriptorCount(1); + geometryUBDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + geometryUBDSLB.pImmutableSamplers(null); + geometryUBDSLB.stageFlags(VK_SHADER_STAGE_GEOMETRY_BIT); + + + VkDescriptorSetLayoutCreateInfo layoutInfo = VkDescriptorSetLayoutCreateInfo.callocStack(stack); + layoutInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO); + layoutInfo.pBindings(bindings); + + LongBuffer pDescriptorSetLayout = stack.mallocLong(1); + + ret = vkCreateDescriptorSetLayout(CVKDevice.GetVkDevice(), layoutInfo, null, pDescriptorSetLayout); + if (VkSucceeded(ret)) { + hDescriptorLayout = pDescriptorSetLayout.get(0); + GetLogger().info("CVKLinksRenderable created hDescriptorLayout: 0x%016X", hDescriptorLayout); + } + } + return ret; + } + + private void DestroyDescriptorLayout() { + GetLogger().info("CVKLinksRenderable destroying hDescriptorLayout: 0x%016X", hDescriptorLayout); + vkDestroyDescriptorSetLayout(CVKDevice.GetVkDevice(), hDescriptorLayout, null); + hDescriptorLayout = VK_NULL_HANDLE; + } + + private int CreateDescriptorSets(MemoryStack stack) { + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + // The same layout is used for each descriptor set. Each image has a + // an identical copy of the descriptor set so allow the GPU and CPU to + // desynchronise (when there are 2 or more images in the swapchain). + final int imageCount = cvkSwapChain.GetImageCount(); + LongBuffer layouts = stack.mallocLong(imageCount); + for (int i = 0; i < imageCount; ++i) { + layouts.put(i, hDescriptorLayout); + } + + VkDescriptorSetAllocateInfo allocInfo = VkDescriptorSetAllocateInfo.callocStack(stack); + allocInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO); + allocInfo.descriptorPool(cvkDescriptorPool.GetDescriptorPoolHandle()); + allocInfo.pSetLayouts(layouts); + + // Allocate the descriptor sets from the descriptor pool, they'll be unitialised + pDescriptorSets = MemoryUtil.memAllocLong(imageCount); + ret = vkAllocateDescriptorSets(CVKDevice.GetVkDevice(), allocInfo, pDescriptorSets); + if (VkFailed(ret)) { return ret; } + + for (int i = 0; i < pDescriptorSets.capacity(); ++i) { + GetLogger().info("CVKLinksRenderable allocated hDescriptorSet %d: 0x%016X", i, pDescriptorSets.get(i)); + } + + return UpdateDescriptorSets(stack); + } + + // TODO: do we gain anything by having buffered UBOs? + private int UpdateDescriptorSets(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(pDescriptorSets); + CVKAssert(pDescriptorSets.capacity() > 0); + CVKAssertNotNull(vertexUniformBuffers); + CVKAssert(vertexUniformBuffers.size() > 0); + CVKAssertNotNull(geometryUniformBuffers); + CVKAssert(geometryUniformBuffers.size() > 0); + + int ret = VK_SUCCESS; + + final int imageCount = cvkSwapChain.GetImageCount(); + + final long positionBufferSize = cvkVisualProcessor.GetPositionBufferSize(); + hPositionBuffer = cvkVisualProcessor.GetPositionBufferHandle(); + hPositionBufferView = cvkVisualProcessor.GetPositionBufferViewHandle(); + CVKAssertNotNull(hPositionBuffer); + CVKAssertNotNull(hPositionBufferView); + + // - Descriptor info structs - + // We create these to describe the different resources we want to address + // in shaders. We have one info struct per resource. We then create a + // write descriptor set structure for each resource for each image. For + // buffered resources like the the uniform buffers we wait to set the + // buffer resource until the image loop below. + + // Struct for the uniform buffer used by VertexIcon.vs + VkDescriptorBufferInfo.Buffer vertexUniformBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + // vertexUniformBufferInfo.buffer is set per imageIndex + vertexUniformBufferInfo.offset(0); + vertexUniformBufferInfo.range(VertexUniformBufferObject.SizeOf()); + + // Struct for texel buffer (positions) used by VertexIcon.vs + VkDescriptorBufferInfo.Buffer positionsTexelBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + positionsTexelBufferInfo.buffer(hPositionBuffer); + positionsTexelBufferInfo.offset(0); + positionsTexelBufferInfo.range(positionBufferSize); + + // Struct for the uniform buffer used by VertexIcon.gs + VkDescriptorBufferInfo.Buffer geometryUniformBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + // geometryBufferInfo.buffer is set per imageIndex + geometryUniformBufferInfo.offset(0); + geometryUniformBufferInfo.range(GeometryUniformBufferObject.SizeOf()); + + // We need 3 write descriptors, 2 for uniform buffers and 1 for texel buffers + VkWriteDescriptorSet.Buffer descriptorWrites = VkWriteDescriptorSet.callocStack(3, stack); + + // Vertex uniform buffer + VkWriteDescriptorSet vertexUBDescriptorWrite = descriptorWrites.get(0); + vertexUBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + vertexUBDescriptorWrite.dstBinding(0); + vertexUBDescriptorWrite.dstArrayElement(0); + vertexUBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + vertexUBDescriptorWrite.descriptorCount(1); + vertexUBDescriptorWrite.pBufferInfo(vertexUniformBufferInfo); + + // Vertex texel buffer (positions) + VkWriteDescriptorSet positionsTBDescriptorWrite = descriptorWrites.get(1); + positionsTBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + positionsTBDescriptorWrite.dstBinding(1); + positionsTBDescriptorWrite.dstArrayElement(0); + positionsTBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + positionsTBDescriptorWrite.descriptorCount(1); + positionsTBDescriptorWrite.pBufferInfo(positionsTexelBufferInfo); + positionsTBDescriptorWrite.pTexelBufferView(stack.longs(hPositionBufferView)); + + // Geometry uniform buffer + VkWriteDescriptorSet geometryUBDescriptorWrite = descriptorWrites.get(2); + geometryUBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + geometryUBDescriptorWrite.dstBinding(2); + geometryUBDescriptorWrite.dstArrayElement(0); + geometryUBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + geometryUBDescriptorWrite.descriptorCount(1); + geometryUBDescriptorWrite.pBufferInfo(geometryUniformBufferInfo); + + for (int i = 0; i < imageCount; ++i) { + // Update the buffered resource buffers + vertexUniformBufferInfo.buffer(vertexUniformBuffers.get(i).GetBufferHandle()); + geometryUniformBufferInfo.buffer(geometryUniformBuffers.get(i).GetBufferHandle()); + + // Set the descriptor set we're updating in each write struct + long descriptorSet = pDescriptorSets.get(i); + descriptorWrites.forEach(el -> {el.dstSet(descriptorSet);}); + + // Update the descriptors with a write and no copy + GetLogger().info("CVKLinksRenderable updating descriptorSet: 0x%016X", descriptorSet); + vkUpdateDescriptorSets(CVKDevice.GetVkDevice(), descriptorWrites, null); + } + + SetDescriptorSetsState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private int DestroyDescriptorSets() { + int ret = VK_SUCCESS; + + if (pDescriptorSets != null) { + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(cvkDescriptorPool.GetDescriptorPoolHandle()); + GetLogger().fine("CVKLinksRenderable returning %d descriptor sets to the pool", pDescriptorSets.capacity()); + + for (int i = 0; i < pDescriptorSets.capacity(); ++i) { + GetLogger().info("CVKLinksRenderable freeing hDescriptorSet %d: 0x%016X", i, pDescriptorSets.get(i)); + } + + // After calling vkFreeDescriptorSets, all descriptor sets in pDescriptorSets are invalid. + ret = vkFreeDescriptorSets(CVKDevice.GetVkDevice(), cvkDescriptorPool.GetDescriptorPoolHandle(), pDescriptorSets); + pDescriptorSets = null; + checkVKret(ret); + } + + return ret; + } + + @Override + public int DestroyDescriptorPoolResources() { + int ret = VK_SUCCESS; + + if (cvkDescriptorPool != null) { + return DestroyDescriptorSets(); + } + + return ret; + } + + @Override + public void IncrementDescriptorTypeRequirements(CVKDescriptorPoolRequirements reqs, CVKDescriptorPoolRequirements perImageReqs) { + // Line.vs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]; + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER]; + + // LineLine.gs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]; + + // One set per image + ++perImageReqs.poolDesciptorSetCount; + } + + @Override + public int SetNewDescriptorPool(CVKDescriptorPool newDescriptorPool) { + int ret = super.SetNewDescriptorPool(newDescriptorPool); + if (VkFailed(ret)) { return ret; } + SetDescriptorSetsState(CVK_RESOURCE_NEEDS_REBUILD); + return ret; + } + + + // ========================> Pipelines <======================== \\ + + private int CreatePipelineLayout() { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(hDescriptorLayout); + + int ret; + try (MemoryStack stack = stackPush()) { + VkPushConstantRange.Buffer pushConstantRange; + pushConstantRange = VkPushConstantRange.calloc(2); + pushConstantRange.get(0).stageFlags(MODEL_VIEW_PUSH_CONSTANT_STAGES); + pushConstantRange.get(0).size(MODEL_VIEW_PUSH_CONSTANT_SIZE); + pushConstantRange.get(0).offset(0); + + pushConstantRange.get(1).stageFlags(HIT_TEST_PUSH_CONSTANT_STAGES); + pushConstantRange.get(1).size(HIT_TEST_PUSH_CONSTANT_SIZE); + pushConstantRange.get(1).offset(MODEL_VIEW_PUSH_CONSTANT_SIZE); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = VkPipelineLayoutCreateInfo.callocStack(stack); + pipelineLayoutInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO); + pipelineLayoutInfo.pSetLayouts(stack.longs(hDescriptorLayout)); + pipelineLayoutInfo.pPushConstantRanges(pushConstantRange); + LongBuffer pPipelineLayout = stack.longs(VK_NULL_HANDLE); + ret = vkCreatePipelineLayout(CVKDevice.GetVkDevice(), pipelineLayoutInfo, null, pPipelineLayout); + if (VkFailed(ret)) { return ret; } + hPipelineLayout = pPipelineLayout.get(0); + CVKAssert(hPipelineLayout != VK_NULL_HANDLE); + } + return ret; + } + + private void DestroyPipelineLayout() { + if (hPipelineLayout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(CVKDevice.GetVkDevice(), hPipelineLayout, null); + hPipelineLayout = VK_NULL_HANDLE; + } + } + + + // ========================> Display <======================== \\ + + @Override + public boolean NeedsDisplayUpdate() { + // Race condition: icons owns the position buffer and the update task (on + // the visual processor thread) may not yet be complete. If this happens + // before the first update is finished icons will have a vertexCount of + // 0 so it won't be able to create the position buffer yet. If we can't + // get a handle to the current position buffer then we just skip updating + // until we can. + if (cvkVisualProcessor.GetPositionBufferHandle() == VK_NULL_HANDLE) { + return false; + } + + if (hPositionBuffer != cvkVisualProcessor.GetPositionBufferHandle() || + hPositionBufferView != cvkVisualProcessor.GetPositionBufferViewHandle()) { + if (descriptorSetsState != CVK_RESOURCE_NEEDS_REBUILD) { + descriptorSetsState = CVK_RESOURCE_NEEDS_UPDATE; + } + } + + return vertexCount > 0 && + (vertexUniformBufferState != CVK_RESOURCE_CLEAN || + geometryUniformBufferState != CVK_RESOURCE_CLEAN || + vertexBuffersState != CVK_RESOURCE_CLEAN || + commandBuffersState != CVK_RESOURCE_CLEAN || + descriptorSetsState != CVK_RESOURCE_CLEAN || + pipelinesState != CVK_RESOURCE_CLEAN); + } + + @Override + public int DisplayUpdate() { + int ret = VK_SUCCESS; + cvkVisualProcessor.VerifyInRenderThread(); + + try (MemoryStack stack = stackPush()) { + // Update vertex buffers + if (vertexBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + DestroyVertexBuffers(); + ret = CreateVertexBuffers(); + if (VkFailed(ret)) { return ret; } + } else if (vertexBuffersState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateVertexBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Vertex uniform buffer + if (vertexUniformBufferState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateVertexUniformBuffers(); + if (VkFailed(ret)) { return ret; } + } else if (vertexUniformBufferState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateVertexUniformBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Geometry uniform buffer + if (geometryUniformBufferState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateGeometryUniformBuffers(); + if (VkFailed(ret)) { return ret; } + } else if (geometryUniformBufferState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateGeometryUniformBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Descriptors (binding values to shaders parameters) + if (descriptorSetsState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateDescriptorSets(stack); + if (VkFailed(ret)) { return ret; } + } else if (descriptorSetsState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateDescriptorSets(stack); + if (VkFailed(ret)) { return ret; } + } + + // Command buffers (rendering commands enqueued on the GPU) + if (commandBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateCommandBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Pipelines (all the render state and resources in one object) + if (pipelinesState == CVK_RESOURCE_NEEDS_REBUILD) { + displayPipelines = new ArrayList<>(cvkSwapChain.GetImageCount()); + ret = CreatePipelines(cvkSwapChain.GetRenderPassHandle(), displayPipelines); + if (VkFailed(ret)) { return ret; } + + hitTestPipelines = new ArrayList<>(cvkSwapChain.GetImageCount()); + ret = CreatePipelines(cvkSwapChain.GetOffscreenRenderPassHandle(), hitTestPipelines); + if (VkFailed(ret)) { return ret; } + } + } + + return ret; + } + + + // ========================> Tasks <======================== \\ + // NOTE: we effectively have two levels of staging. The second level is pretty + // straightforward, we need the resources we render with to be in the most + // optimal memory possible: resident GPU memory. This memory cannot be written + // by the CPU so we can't update it directly, instead we update staging buffers + // that are both GPU and CPU writable then copy from that into the final GPU + // buffers. + // The first level of staging is required as our staging buffers are VkDevice + // resources and the device may not be initialised when these tasks are called. + // This is the case when a graph is loaded into a new tab, we need to be able + // to process the tasks that load the graph vertices etc before the device is + // ready to create staging buffers. For this reason we update local arrays + // in the BuildArray functions, then copy these to our staging buffers + // during the renderer's display loop (when the rendering lambda of each task + // is executed). This also means we don't need to synchronise these arrays + // created by the visual processor's thread with the staging buffers that have + // a lifespan entirely within the rendering thread (AWT event thread). This + // is possible because the arrays are locals in the task functions and aren't + // modified by the visual processor thread after they've been added to the + // queue of tasks for the renderer to process. + + + /** + * + * @param access: the view of this graph + * @param first: the index of the first link to process + * @param last: the index of the last link to process + * @return: an array of vertex objects ready to copy into the VB + */ + private Vertex[] BuildVertexArray(final VisualAccess access, int first, int last) { + Vertex[] vertices = null; + if ((last - first) >= 0) { + final SortedMap connectionPosToBufferPos = new TreeMap<>(); + final List connections = new ArrayList<>(); + + int lineCounter = 0; + for (int link = first; link <= last; ++link) { + if (access.getLinkSource(link) != access.getLinkDestination(link)) { + connections.add(NEW_LINK); + for (int pos = 0; pos < access.getLinkConnectionCount(link); pos++) { + final int connection = access.getLinkConnection(link, pos); + connectionPosToBufferPos.put(connection, lineCounter++); + connections.add(connection); + } + } + } + + vertices = new Vertex[lineCounter * 2]; + int iConnection = 0; + for (int pos : connections) { + if (pos == NEW_LINK) { + leftOffset = 0; + } else { + if (connectionPosToBufferPos.containsKey(pos)) { + final float width = Math.min(LabelUtilities.MAX_TRANSACTION_WIDTH, access.getConnectionWidth(pos)); + final float offset; + if (leftOffset == 0) { + offset = 0; + leftOffset += width / 2; + rightOffset = leftOffset; + } else if (leftOffset < rightOffset) { + offset = -(leftOffset + width / 2 + 1); + leftOffset += width + 1; + } else { + offset = rightOffset + width / 2 + 1; + rightOffset += width + 1; + } + + final int representativeTransactionId = access.getConnectionId(pos); + final int lowVertex = access.getConnectionLowVertex(pos); + final int highVertex = access.getConnectionHighVertex(pos); + final int flags = (access.getConnectionDimmed(pos) ? 2 : 0) | (access.getConnectionSelected(pos) ? 1 : 0); + final int lineStyle = access.getConnectionLineStyle(pos).ordinal(); + final ConnectionDirection connectionDirection = access.getConnectionDirection(pos); + + int iStart = iConnection * 2; + int iEnd = iStart + 1; + ++iConnection; + CVKAssert(iStart < vertices.length); + CVKAssert(iEnd < vertices.length); + + final ConstellationColor colour = access.getConnectionColor(pos); + final float visibility = access.getConnectionVisibility(pos); + vertices[iStart] = new Vertex(colour, + visibility, + representativeTransactionId, + lowVertex * LINE_INFO_BITS_AVOID + ((connectionDirection == ConnectionDirection.LOW_TO_HIGH || connectionDirection == ConnectionDirection.BIDIRECTED) ? LINE_INFO_ARROW : 0), + flags, + (int) (offset * FLOAT_MULTIPLIER)); + vertices[iEnd] = new Vertex(colour, + visibility, + representativeTransactionId, + highVertex * LINE_INFO_BITS_AVOID + ((connectionDirection == ConnectionDirection.HIGH_TO_LOW || connectionDirection == ConnectionDirection.BIDIRECTED) ? LINE_INFO_ARROW : 0), + flags, + ((int) (width * FLOAT_MULTIPLIER)) << 2 | lineStyle); + } + } + } + } + + return vertices; + } + + private void RebuildVertexStagingBuffer(Vertex[] vertices) { + vertexCount = (vertices != null ? vertices.length : 0); + final int newSizeBytes = vertexCount * Vertex.BYTES; + final boolean recreate = cvkVertexStagingBuffer == null || newSizeBytes != cvkVertexStagingBuffer.GetBufferSize(); + + if (recreate) { + if (cvkVertexStagingBuffer != null) { + cvkVertexStagingBuffer.Destroy(); + cvkVertexStagingBuffer = null; + } + + if (newSizeBytes > 0) { + cvkVertexStagingBuffer = CVKBuffer.Create(newSizeBytes, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKLabelsRenderable.RebuildVertexStagingBuffer cvkVertexStagingBuffer"); + } + } + + if (newSizeBytes > 0) { + UpdateVertexStagingBuffer(vertices, 0, vertices.length - 1); + } + } + + private void UpdateVertexStagingBuffer(Vertex[] vertices, int first, int last) { + CVKAssertNotNull(cvkVertexStagingBuffer); + CVKAssertNotNull(vertices != null); + CVKAssert(vertices.length > 0 && vertices.length > (last - first)); + CVKAssert(last >= 0 && last >= first && first >= 0); + + int offset = first * Vertex.BYTES; + int size = ((last - first) + 1) * Vertex.BYTES; + + ByteBuffer pMemory = cvkVertexStagingBuffer.StartMemoryMap(offset, size); + for (Vertex vertex : vertices) { + vertex.CopyToSequentially(pMemory); + } + cvkVertexStagingBuffer.EndMemoryMap(); + pMemory = null; // now unmapped, do not use + } + + public CVKRenderUpdateTask TaskUpdateLinks(final VisualChange change, final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + GetLogger().fine("TaskUpdateLinks frame %d: %d verts", cvkVisualProcessor.GetFrameNumber(), access.getVertexCount()); + + final boolean rebuildRequired = cvkVertexStagingBuffer == null || + access.getLinkCount() * Vertex.BYTES != cvkVertexStagingBuffer.GetBufferSize() || + change.isEmpty(); + final int changedVerticeRange[]; + final Vertex vertexArray[]; + if (rebuildRequired) { + vertexArray = BuildVertexArray(access, 0, access.getLinkCount() - 1); + changedVerticeRange = null; + } else { + changedVerticeRange = change.getRange(); + vertexArray = BuildVertexArray(access, changedVerticeRange[0], changedVerticeRange[1]); + } + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + if (rebuildRequired) { + RebuildVertexStagingBuffer(vertexArray); + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + vertexCount = vertexArray != null ? vertexArray.length : 0; + } else if (vertexBuffersState != CVK_RESOURCE_NEEDS_REBUILD) { + UpdateVertexStagingBuffer(vertexArray, changedVerticeRange[0], changedVerticeRange[1]); + SetVertexBuffersState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + // GraphVisualAccess sends an empty changelist after building the visual + // change with what looks like the full set of transactions. If this is + // ever fixed so we receive just the transaction that was selected implement + // this method and change CVKVisualProcessor to call it instead of TaskUpdateLinks + // for CONNECTION_SELECTED and CONNECTION_COLOR. +// public CVKRenderUpdateTask TaskConnectionSelected(final VisualChange change, final VisualAccess access) +// public CVKRenderUpdateTask TaskUpdateColours(final VisualChange change, final VisualAccess access) + + + public CVKRenderUpdateTask TaskSetHighlightColour(final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + final ConstellationColor updatedHighlightColour = access.getHighlightColor(); + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + highlightColour.set(updatedHighlightColour.getRed(), + updatedHighlightColour.getGreen(), + updatedHighlightColour.getBlue(), + updatedHighlightColour.getAlpha()); + }; + } + + public CVKRenderUpdateTask TaskUpdateOpacity(final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + final float updatedOpacity = access.getConnectionOpacity(); + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + opacity = updatedOpacity; + }; + } + + public CVKRenderUpdateTask TaskUpdateCamera() { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + UpdateVertexPushConstants(); + }; + } + + + + // ========================> Helpers <======================== \\ + + protected long GetVertexBufferHandle(int imageIndex) { return vertexBuffers.get(imageIndex).GetBufferHandle(); } + protected long GetVertexUniformBufferHandle(int imageIndex) { return vertexUniformBuffers.get(imageIndex).GetBufferHandle(); } + protected long GetGeometryUniformBufferHandle(int imageIndex) { return geometryUniformBuffers.get(imageIndex).GetBufferHandle(); } + protected ByteBuffer GetModelViewPushConstants() { return modelViewPushConstants; } + protected int GetModelViewPushConstantsSize() { return MODEL_VIEW_PUSH_CONSTANT_SIZE; } + protected ByteBuffer GetHitTestPushConstants() { return hitTestPushConstants; } + protected int GetHitTestPushConstantsSize() { return HIT_TEST_PUSH_CONSTANT_SIZE; } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKLoopsRenderable.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKLoopsRenderable.java new file mode 100644 index 0000000000..fa12e26b99 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKLoopsRenderable.java @@ -0,0 +1,1389 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.renderables; + +import au.gov.asd.tac.constellation.utilities.color.ConstellationColor; +import au.gov.asd.tac.constellation.utilities.graphics.Matrix44f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector4f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector4i; +import au.gov.asd.tac.constellation.utilities.visual.VisualAccess; +import au.gov.asd.tac.constellation.utilities.visual.VisualChange; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool.CVKDescriptorPoolRequirements; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import au.gov.asd.tac.constellation.visual.vulkan.CVKRenderUpdateTask; +import au.gov.asd.tac.constellation.visual.vulkan.CVKSwapChain; +import au.gov.asd.tac.constellation.visual.vulkan.CVKVisualProcessor; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_CLEAN; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_NEEDS_REBUILD; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_NEEDS_UPDATE; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKBuffer; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKCommandBuffer; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKIconTextureAtlas; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssert; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssertNotNull; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssertNull; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.IDENTITY_44F; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.PutMatrix44f; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkFailed; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkSucceeded; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.checkVKret; +import java.nio.ByteBuffer; +import java.nio.LongBuffer; +import java.util.ArrayList; +import java.util.List; +import org.lwjgl.system.MemoryStack; +import static org.lwjgl.system.MemoryStack.stackPush; +import org.lwjgl.system.MemoryUtil; +import static org.lwjgl.system.MemoryUtil.memFree; +import static org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_TRANSFER_DST_BIT; +import static org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_TRANSFER_SRC_BIT; +import static org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; +import static org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_LEVEL_SECONDARY; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT; +import static org.lwjgl.vulkan.VK10.VK_CULL_MODE_NONE; +import static org.lwjgl.vulkan.VK10.VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; +import static org.lwjgl.vulkan.VK10.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +import static org.lwjgl.vulkan.VK10.VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32B32A32_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32B32A32_SINT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; +import static org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; +import static org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; +import static org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.VK_SHADER_STAGE_FRAGMENT_BIT; +import static org.lwjgl.vulkan.VK10.VK_SHADER_STAGE_GEOMETRY_BIT; +import static org.lwjgl.vulkan.VK10.VK_SHADER_STAGE_VERTEX_BIT; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; +import static org.lwjgl.vulkan.VK10.VK_SUCCESS; +import static org.lwjgl.vulkan.VK10.VK_VERTEX_INPUT_RATE_VERTEX; +import static org.lwjgl.vulkan.VK10.vkAllocateDescriptorSets; +import static org.lwjgl.vulkan.VK10.vkCreateDescriptorSetLayout; +import static org.lwjgl.vulkan.VK10.vkCreatePipelineLayout; +import static org.lwjgl.vulkan.VK10.vkDestroyDescriptorSetLayout; +import static org.lwjgl.vulkan.VK10.vkDestroyPipelineLayout; +import static org.lwjgl.vulkan.VK10.vkFreeDescriptorSets; +import static org.lwjgl.vulkan.VK10.vkUpdateDescriptorSets; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkCommandBufferInheritanceInfo; +import org.lwjgl.vulkan.VkDescriptorBufferInfo; +import org.lwjgl.vulkan.VkDescriptorImageInfo; +import org.lwjgl.vulkan.VkDescriptorSetAllocateInfo; +import org.lwjgl.vulkan.VkDescriptorSetLayoutBinding; +import org.lwjgl.vulkan.VkDescriptorSetLayoutCreateInfo; +import org.lwjgl.vulkan.VkPipelineLayoutCreateInfo; +import org.lwjgl.vulkan.VkPushConstantRange; +import org.lwjgl.vulkan.VkVertexInputAttributeDescription; +import org.lwjgl.vulkan.VkVertexInputBindingDescription; +import org.lwjgl.vulkan.VkWriteDescriptorSet; + + +/******************************************************************************* + * CVKLoopsRenderable + * + * + *******************************************************************************/ + +public class CVKLoopsRenderable extends CVKRenderable { + // Resources recreated with the swap chain (dependent on the image count) + private LongBuffer pDescriptorSets = null; + private List hitTestPipelines = null; + private List displayCommandBuffers = null; + private List hittestCommandBuffers = null; + private List vertexBuffers = null; + private List vertexUniformBuffers = null; + private List geometryUniformBuffers = null; + + // The UBO staging buffers are a known size so created outside user events + private CVKBuffer cvkVertexUBStagingBuffer = null; + private CVKBuffer cvkGeometryUBStagingBuffer = null; + private final VertexUniformBufferObject vertexUBO = new VertexUniformBufferObject(); + private final GeometryUniformBufferObject geometryUBO = new GeometryUniformBufferObject(); + + // Resources recreated only through user events + private int vertexCount = 0; + private CVKBuffer cvkVertexStagingBuffer = null; + + // Resources we don't own but use and must track so we know when to update + // our descriptors + private long hPositionBuffer = VK_NULL_HANDLE; + private long hPositionBufferView = VK_NULL_HANDLE; + private long hIconAtlasSampler = VK_NULL_HANDLE; + private long hIconAtlasImageView = VK_NULL_HANDLE; + + // Push constants for shaders contains the MV matrix and drawHitTest int + private ByteBuffer modelViewPushConstants = null; + private ByteBuffer hitTestPushConstants = null; + private static final int MODEL_VIEW_PUSH_CONSTANT_STAGES = VK_SHADER_STAGE_VERTEX_BIT; + private static final int HIT_TEST_PUSH_CONSTANT_STAGES = VK_SHADER_STAGE_FRAGMENT_BIT; + private static final int MODEL_VIEW_PUSH_CONSTANT_SIZE = Matrix44f.BYTES; + private static final int HIT_TEST_PUSH_CONSTANT_SIZE = Integer.BYTES; + + + // ========================> Classes <======================== \\ + + protected static class Vertex { + // This looks a little weird for Java, but LWJGL and JOGL both require + // contiguous memory which is passed to the native GL or VK libraries. + private static final int BYTES = Vector4f.BYTES + Vector4i.BYTES; + private static final int OFFSETOF_DATA = Vector4f.BYTES; + private static final int OFFSET_CLR = 0; + private static final int BINDING = 0; + private Vector4f colour = new Vector4f(); + private Vector4i data = new Vector4i(); + + private Vertex() {} + + + public Vertex(ConstellationColor inColour, + float visibility, + int connectionID, + int vertexID, + int flags, + int iconID) { + colour.a[0] = inColour.getRed(); + colour.a[1] = inColour.getGreen(); + colour.a[2] = inColour.getBlue(); + colour.a[3] = visibility; + data.a[0] = connectionID; + data.a[1] = vertexID; + data.a[2] = flags; + data.a[3] = iconID; + } + + + + public void CopyToSequentially(ByteBuffer buffer) { + buffer.putFloat(colour.a[0]); + buffer.putFloat(colour.a[1]); + buffer.putFloat(colour.a[2]); + buffer.putFloat(colour.a[3]); + buffer.putInt(data.a[0]); + buffer.putInt(data.a[1]); + buffer.putInt(data.a[2]); + buffer.putInt(data.a[3]); + } + + /** + * A VkVertexInputBindingDescription defines the rate at which data is + * consumed by vertex shader (per vertex or per instance). + * The input rate determines whether to move to the next data entry after + * each vertex or after each instance. + * The binding description also defines the vertex stride, the number of + * bytes that must be stepped from vertex n-1 to vertex n. + * + * @return Binding description for the FPS vertex type + */ + protected static VkVertexInputBindingDescription.Buffer GetBindingDescription() { + + VkVertexInputBindingDescription.Buffer bindingDescription = + VkVertexInputBindingDescription.callocStack(1); + + // If we bind multiple vertex buffers with different descriptions + // this is the index of this description occupies in the array of + // bound descriptions. + bindingDescription.binding(BINDING); + bindingDescription.stride(Vertex.BYTES); + bindingDescription.inputRate(VK_VERTEX_INPUT_RATE_VERTEX); + + return bindingDescription; + } + + /** + * A VkVertexInputAttributeDescription describes each element int the + * vertex buffer. + * binding: matches the binding member of VkVertexInputBindingDescription + * location: corresponds to the layout(location = #) in the vertex shader + * for this element (0 for data, 1 for bkgClr). + * format: format the shader will interpret this as. + * offset: bytes from the start of the vertex this attribute starts at + * + * @return + */ + protected static VkVertexInputAttributeDescription.Buffer GetAttributeDescriptions() { + + VkVertexInputAttributeDescription.Buffer attributeDescriptions = + VkVertexInputAttributeDescription.callocStack(2); + + // vColor + VkVertexInputAttributeDescription posDescription = attributeDescriptions.get(0); + posDescription.binding(BINDING); + posDescription.location(0); + posDescription.format(VK_FORMAT_R32G32B32A32_SFLOAT); + posDescription.offset(OFFSET_CLR); + + // data + VkVertexInputAttributeDescription colorDescription = attributeDescriptions.get(1); + colorDescription.binding(BINDING); + colorDescription.location(1); + colorDescription.format(VK_FORMAT_R32G32B32A32_SINT); + colorDescription.offset(OFFSETOF_DATA); + + return attributeDescriptions.rewind(); + } + } + + @Override + protected VkVertexInputBindingDescription.Buffer GetVertexBindingDescription() { + return Vertex.GetBindingDescription(); + } + + @Override + protected VkVertexInputAttributeDescription.Buffer GetVertexAttributeDescriptions() { + return Vertex.GetAttributeDescriptions(); + } + + protected static class VertexUniformBufferObject { + public float morphMix = 0; + private static Integer padding = null; + + protected static int SizeOf() { + if (padding == null) { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + final int minAlignment = CVKDevice.GetMinUniformBufferAlignment(); + + // The matrices are 64 bytes each so should line up on a boundary (unless the minimum alignment is huge) + CVKAssert(minAlignment <= (16 * Float.BYTES)); + + int sizeof = 1 * Float.BYTES; // morphMix + final int overrun = sizeof % minAlignment; + padding = overrun > 0 ? minAlignment - overrun : 0; + } + + return 1 * Float.BYTES + // morphMix + padding; + } + + private void CopyTo(ByteBuffer buffer) { + buffer.putFloat(morphMix); + + for (int i = 0; i < padding; ++i) { + buffer.put((byte)0); + } + } + } + + protected static class GeometryUniformBufferObject { + private final Matrix44f pMatrix = new Matrix44f(); + private float visibilityLow; + private float visibilityHigh; + private int iconsPerRowColumn; + private int iconsPerLayer; + private int atlas2DDimension; + private static Integer padding = null; + + protected static int SizeOf() { + if (padding == null) { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + final int minAlignment = CVKDevice.GetMinUniformBufferAlignment(); + + // The matrices are 64 bytes each so should line up on a boundary (unless the minimum alignment is huge) + CVKAssert(minAlignment <= (Matrix44f.BYTES)); + + int sizeof = Matrix44f.BYTES + // pMatrix + 1 * Float.BYTES + // visibilityLow + 1 * Float.BYTES + // visibilityHigh + 1 * Integer.BYTES + // iconsPerRowColumn + 1 * Integer.BYTES + // iconsPerLayer + 1 * Integer.BYTES; // atlas2DDimension + + final int overrun = sizeof % minAlignment; + padding = overrun > 0 ? minAlignment - overrun : 0; + } + + return Matrix44f.BYTES + // pMatrix + 1 * Float.BYTES + // visibilityLow + 1 * Float.BYTES + // visibilityHigh + 1 * Integer.BYTES + // iconsPerRowColumn + 1 * Integer.BYTES + // iconsPerLayer + 1 * Integer.BYTES + // atlas2DDimension + padding; + } + + private void CopyTo(ByteBuffer buffer) { + PutMatrix44f(buffer, pMatrix); + + buffer.putFloat(visibilityLow); + buffer.putFloat(visibilityHigh); + buffer.putInt(iconsPerRowColumn); + buffer.putInt(iconsPerLayer); + buffer.putInt(atlas2DDimension); + + for (int i = 0; i < padding; ++i) { + buffer.put((byte)0); + } + } + } + + + // ========================> Shaders <======================== \\ + + @Override + protected String GetVertexShaderName() { return "Loop.vs"; } + + @Override + protected String GetGeometryShaderName() { return "Loop.gs"; } + + @Override + protected String GetFragmentShaderName() { return "Loop.fs"; } + + + // ========================> Lifetime <======================== \\ + + public CVKLoopsRenderable(CVKVisualProcessor visualProcessor) { + super(visualProcessor); + } + + private void CreateUBOStagingBuffers() { + cvkVertexUBStagingBuffer = CVKBuffer.Create(VertexUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKLoopsRenderable.CreateUBOStagingBuffers cvkVertexUBStagingBuffer"); + cvkGeometryUBStagingBuffer = CVKBuffer.Create(GeometryUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKLoopsRenderable.CreateUBOStagingBuffers cvkGeometryUBStagingBuffer"); + } + + @Override + public int Initialise() { + int ret = super.Initialise(); + if (VkFailed(ret)) { return ret; } + + // Check for double initialisation + CVKAssert(hDescriptorLayout == VK_NULL_HANDLE); + + CreatePushConstants(); + + ret = CreateDescriptorLayout(); + if (VkFailed(ret)) { return ret; } + + ret = CreatePipelineLayout(); + if (VkFailed(ret)) { return ret; } + + CreateUBOStagingBuffers(); + + return ret; + } + + private void DestroyStagingBuffers() { + if (cvkVertexStagingBuffer != null) { + cvkVertexStagingBuffer.Destroy(); + cvkVertexStagingBuffer = null; + } + if (cvkVertexUBStagingBuffer != null) { + cvkVertexUBStagingBuffer.Destroy(); + cvkVertexUBStagingBuffer = null; + } + if (cvkGeometryUBStagingBuffer != null) { + cvkGeometryUBStagingBuffer.Destroy(); + cvkGeometryUBStagingBuffer = null; + } + } + + @Override + public void Destroy() { + DestroyVertexBuffers(); + DestroyVertexUniformBuffers(); + DestroyGeometryUniformBuffers(); + DestroyDescriptorSets(); + DestroyDescriptorLayout(); + DestroyPipelines(); + DestroyPipelineLayout(); + DestroyCommandBuffers(); + DestroyStagingBuffers(); + DestroyPushConstants(); + + CVKAssertNull(vertexBuffers); + CVKAssertNull(vertexUniformBuffers); + CVKAssertNull(geometryUniformBuffers); + CVKAssertNull(pDescriptorSets); + CVKAssertNull(hDescriptorLayout); + CVKAssertNull(displayCommandBuffers); + CVKAssertNull(displayPipelines); + CVKAssertNull(hPipelineLayout); + CVKAssertNull(modelViewPushConstants); + CVKAssertNull(hitTestPushConstants); + } + + + // ========================> Swap chain <======================== \\ + + @Override + protected int DestroySwapChainResources() { + this.cvkSwapChain = null; + + // We only need to recreate these resources if the number of images in + // the swapchain changes or if this is the first call after the initial + // swapchain is created. + if (displayPipelines != null && swapChainImageCountChanged) { + DestroyVertexBuffers(); + DestroyVertexUniformBuffers(); + DestroyGeometryUniformBuffers(); + DestroyDescriptorSets(); + DestroyCommandBuffers(); + DestroyPipelines(); + DestroyCommandBuffers(); + } + + return VK_SUCCESS; + } + + @Override + public int SetNewSwapChain(CVKSwapChain newSwapChain) { + int ret = super.SetNewSwapChain(newSwapChain); + if (VkFailed(ret)) { return ret; } + + if (swapChainImageCountChanged) { + // The number of images has changed, we need to rebuild all image + // buffered resources + SetVertexUniformBufferState(CVK_RESOURCE_NEEDS_REBUILD); + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_REBUILD); + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + SetCommandBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + SetDescriptorSetsState(CVK_RESOURCE_NEEDS_REBUILD); + SetPipelinesState(CVK_RESOURCE_NEEDS_REBUILD); + } else { + // View frustum and projection matrix likely have changed. We don't + // need to rebuild our displayPipelines as the frustum is set by dynamic + // state in RecordDisplayCommandBuffer + if (geometryUniformBufferState != CVK_RESOURCE_NEEDS_REBUILD) { + SetGeometryUniformBufferState(CVK_RESOURCE_NEEDS_UPDATE); + } + } + + return ret; + } + + + // ========================> Vertex buffers <======================== \\ + + private int CreateVertexBuffers() { + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + + // We can only create vertex buffers if we have something to put in them + if (cvkVertexStagingBuffer.GetBufferSize() > 0) { + int imageCount = cvkSwapChain.GetImageCount(); + vertexBuffers = new ArrayList<>(); + + for (int i = 0; i < imageCount; ++i) { + CVKBuffer cvkVertexBuffer = CVKBuffer.Create(cvkVertexStagingBuffer.GetBufferSize(), + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKLoopsRenderable cvkVertexBuffer %d", i)); + vertexBuffers.add(cvkVertexBuffer); + } + + // Populate them with some values + return UpdateVertexBuffers(); + } + + return ret; + } + + private int UpdateVertexBuffers() { + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssert(cvkVertexStagingBuffer != null); + CVKAssert(vertexBuffers != null); + CVKAssert(vertexBuffers.size() > 0); + CVKAssert(cvkVertexStagingBuffer.GetBufferSize() == vertexBuffers.get(0).GetBufferSize()); + int ret = VK_SUCCESS; + + for (int i = 0; i < vertexBuffers.size(); ++i) { + CVKBuffer cvkVertexBuffer = vertexBuffers.get(i); + ret = cvkVertexBuffer.CopyFrom(cvkVertexStagingBuffer); + if (VkFailed(ret)) { return ret; } + } + + // Note the staging buffer is not freed as we can simplify the update tasks + // by just updating it and then copying it over again during ProcessRenderTasks(). + SetVertexBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public int GetVertexCount() { return cvkVisualProcessor.GetDrawFlags().drawConnections() ? vertexCount : 0; } + + private void DestroyVertexBuffers() { + if (vertexBuffers != null) { + vertexBuffers.forEach(el -> {el.Destroy();}); + vertexBuffers.clear(); + vertexBuffers = null; + } + } + + + // ========================> Uniform buffers <======================== \\ + + private int CreateVertexUniformBuffers() { + CVKAssertNotNull(cvkSwapChain); + CVKAssert(vertexUniformBuffers == null); + + vertexUniformBuffers = new ArrayList<>(); + for (int i = 0; i < cvkSwapChain.GetImageCount(); ++i) { + CVKBuffer vertexUniformBuffer = CVKBuffer.Create(VertexUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKLoopsRenderable vertexUniformBuffer %d", i)); + vertexUniformBuffers.add(vertexUniformBuffer); + } + return UpdateVertexUniformBuffers(); + } + + private int UpdateVertexUniformBuffers() { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkVertexUBStagingBuffer); + CVKAssertNotNull(vertexUniformBuffers); + CVKAssert(vertexUniformBuffers.size() > 0); + + int ret = VK_SUCCESS; + + // Populate the UBO. This is easy to deal with, but not super efficient + // as we are effectively staging into the staging buffer below. + vertexUBO.morphMix = cvkVisualProcessor.getDisplayCamera().getMix(); + + // Staging buffer so our VBO can be device local (most performant memory) + ByteBuffer pMemory = cvkVertexUBStagingBuffer.StartMemoryMap(0, VertexUniformBufferObject.SizeOf()); + { + vertexUBO.CopyTo(pMemory); + } + cvkVertexUBStagingBuffer.EndMemoryMap(); + pMemory = null; + + // Copy the staging buffer into the uniform buffer on the device + final int imageCount = cvkSwapChain.GetImageCount(); + for (int i = 0; i < imageCount; ++i) { + ret = vertexUniformBuffers.get(i).CopyFrom(cvkVertexUBStagingBuffer); + if (VkFailed(ret)) { return ret; } + } + + UpdateVertexPushConstants(); + + // We are done, reset the resource state + SetVertexUniformBufferState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private void DestroyVertexUniformBuffers() { + if (vertexUniformBuffers != null) { + vertexUniformBuffers.forEach(el -> {el.Destroy();}); + vertexUniformBuffers = null; + } + } + + private int CreateGeometryUniformBuffers() { + CVKAssertNotNull(cvkSwapChain); + CVKAssert(geometryUniformBuffers == null); + + geometryUniformBuffers = new ArrayList<>(); + for (int i = 0; i < cvkSwapChain.GetImageCount(); ++i) { + CVKBuffer geometryUniformBuffer = CVKBuffer.Create(GeometryUniformBufferObject.SizeOf(), + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + String.format("CVKLoopsRenderable geometryUniformBuffer %d", i)); + geometryUniformBuffers.add(geometryUniformBuffer); + } + return UpdateGeometryUniformBuffers(); + } + + private int UpdateGeometryUniformBuffers() { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkGeometryUBStagingBuffer); + CVKAssertNotNull(geometryUniformBuffers); + CVKAssert(geometryUniformBuffers.size() > 0); + + int ret = VK_SUCCESS; + + // Populate the UBO. This is easy to deal with, but not super efficient + // as we are effectively staging into the staging buffer below. + geometryUBO.pMatrix.set(cvkVisualProcessor.GetProjectionMatrix()); + geometryUBO.visibilityLow = cvkVisualProcessor.getDisplayCamera().getVisibilityLow(); + geometryUBO.visibilityHigh = cvkVisualProcessor.getDisplayCamera().getVisibilityHigh(); + geometryUBO.iconsPerRowColumn = CVKIconTextureAtlas.GetInstance().serializableData.iconsPerRowColumn; + geometryUBO.iconsPerLayer = CVKIconTextureAtlas.GetInstance().serializableData.iconsPerLayer; + geometryUBO.atlas2DDimension = CVKIconTextureAtlas.GetInstance().serializableData.texture2DDimension; + + // Staging buffer so our VBO can be device local (most performant memory) + ByteBuffer pMemory = cvkGeometryUBStagingBuffer.StartMemoryMap(0, GeometryUniformBufferObject.SizeOf()); + { + geometryUBO.CopyTo(pMemory); + } + cvkGeometryUBStagingBuffer.EndMemoryMap(); + pMemory = null; + + // Copy the staging buffer into the uniform buffer on the device + final int imageCount = cvkSwapChain.GetImageCount(); + for (int i = 0; i < imageCount; ++i) { + ret = geometryUniformBuffers.get(i).CopyFrom(cvkGeometryUBStagingBuffer); + if (VkFailed(ret)) { return ret; } + } + + // We are done, reset the resource state + SetGeometryUniformBufferState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private void DestroyGeometryUniformBuffers() { + if (geometryUniformBuffers != null) { + geometryUniformBuffers.forEach(el -> {el.Destroy();}); + geometryUniformBuffers = null; + } + } + + // ========================> Push constants <======================== \\ + + private int CreatePushConstants() { + // Initialise push constants to identity mtx + modelViewPushConstants = MemoryUtil.memAlloc(MODEL_VIEW_PUSH_CONSTANT_SIZE); + PutMatrix44f(modelViewPushConstants, IDENTITY_44F); + + // Set DrawHitTest to false + hitTestPushConstants = MemoryUtil.memAlloc(HIT_TEST_PUSH_CONSTANT_SIZE); + hitTestPushConstants.putInt(0); + + modelViewPushConstants.flip(); + hitTestPushConstants.flip(); + + return VK_SUCCESS; + } + + private void UpdateVertexPushConstants(){ + CVKAssertNotNull(cvkSwapChain); + + modelViewPushConstants.clear(); + PutMatrix44f(modelViewPushConstants, cvkVisualProcessor.getDisplayModelViewMatrix()); + modelViewPushConstants.flip(); + } + + protected void UpdatePushConstantsHitTest(boolean drawHitTest){ + CVKAssertNotNull(cvkSwapChain); + + hitTestPushConstants.clear(); + + if (drawHitTest) { + hitTestPushConstants.putInt(1); + } else { + hitTestPushConstants.putInt(0); + } + + hitTestPushConstants.flip(); + } + + private void DestroyPushConstants() { + if (modelViewPushConstants != null) { + memFree(modelViewPushConstants); + modelViewPushConstants = null; + } + + if (hitTestPushConstants != null) { + memFree(hitTestPushConstants); + hitTestPushConstants = null; + } + } + + + // ========================> Command buffers <======================== \\ + + public int CreateCommandBuffers(){ + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + int imageCount = cvkSwapChain.GetImageCount(); + + displayCommandBuffers = new ArrayList<>(imageCount); + hittestCommandBuffers = new ArrayList<>(imageCount); + + for (int i = 0; i < imageCount; ++i) { + CVKCommandBuffer buffer = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_SECONDARY, GetLogger(), String.format("CVKLoopsRenderable %d", i)); + displayCommandBuffers.add(buffer); + + CVKCommandBuffer offscreenBuffer = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_SECONDARY, GetLogger(), String.format("CVKLoopsRenderable Offscreen Buffer %d", i)); + hittestCommandBuffers.add(offscreenBuffer); + } + + SetCommandBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public VkCommandBuffer GetDisplayCommandBuffer(int imageIndex) { + return displayCommandBuffers.get(imageIndex).GetVKCommandBuffer(); + } + + @Override + public VkCommandBuffer GetHitTestCommandBuffer(int imageIndex) { + return hittestCommandBuffers.get(imageIndex).GetVKCommandBuffer(); + } + + @Override + public int RecordDisplayCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int imageIndex){ + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(CVKDevice.GetCommandPoolHandle()); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + CVKCommandBuffer commandBuffer = displayCommandBuffers.get(imageIndex); + CVKAssert(commandBuffer != null); + CVKAssert(displayPipelines.get(imageIndex) != null); + + commandBuffer.BeginRecordSecondary(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, inheritanceInfo); + + commandBuffer.SetViewPort(cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight()); + commandBuffer.SetScissor(cvkVisualProcessor.GetCanvas().GetCurrentSurfaceExtent()); + + commandBuffer.BindGraphicsPipeline(displayPipelines.get(imageIndex)); + commandBuffer.BindVertexInput(vertexBuffers.get(imageIndex).GetBufferHandle()); + + // Push MV matrix to the vertex shader + commandBuffer.PushConstants(hPipelineLayout, MODEL_VIEW_PUSH_CONSTANT_STAGES, 0, modelViewPushConstants); + + // Push drawHitTest flag to the geometry shader + commandBuffer.PushConstants(hPipelineLayout, HIT_TEST_PUSH_CONSTANT_STAGES, Matrix44f.BYTES, hitTestPushConstants); + + commandBuffer.BindGraphicsDescriptorSets(hPipelineLayout, pDescriptorSets.get(imageIndex)); + + commandBuffer.Draw(GetVertexCount()); + + ret = commandBuffer.FinishRecord(); + if (VkFailed(ret)) { return ret; } + + return ret; + } + + @Override + public int RecordHitTestCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int imageIndex){ + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssert(CVKDevice.GetCommandPoolHandle() != VK_NULL_HANDLE); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + // Set the hit test flag in the shaders to true + UpdatePushConstantsHitTest(true); + + CVKCommandBuffer commandBuffer = hittestCommandBuffers.get(imageIndex); + CVKAssertNotNull(commandBuffer); + CVKAssertNotNull(hitTestPipelines.get(imageIndex)); + + ret = commandBuffer.BeginRecordSecondary(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, + inheritanceInfo); + if (VkFailed(ret)) { return ret; } + + commandBuffer.SetViewPort(cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight()); + commandBuffer.SetScissor(cvkVisualProcessor.GetCanvas().GetCurrentSurfaceExtent()); + + commandBuffer.BindGraphicsPipeline(hitTestPipelines.get(imageIndex)); + commandBuffer.BindVertexInput(vertexBuffers.get(imageIndex).GetBufferHandle()); + + // Push MV matrix to the vertex shader + commandBuffer.PushConstants(hPipelineLayout, + MODEL_VIEW_PUSH_CONSTANT_STAGES, + 0, + modelViewPushConstants); + + // Push drawHitTest flag to the geometry shader + commandBuffer.PushConstants(hPipelineLayout, + HIT_TEST_PUSH_CONSTANT_STAGES, + MODEL_VIEW_PUSH_CONSTANT_SIZE, + hitTestPushConstants); + + commandBuffer.BindGraphicsDescriptorSets(hPipelineLayout, pDescriptorSets.get(imageIndex)); + + commandBuffer.Draw(GetVertexCount()); + + ret = commandBuffer.FinishRecord(); + if (VkFailed(ret)) { return ret; } + + // Reset hit test flag to false + UpdatePushConstantsHitTest(false); + + return ret; + } + + private void DestroyCommandBuffers() { + if (null != displayCommandBuffers) { + displayCommandBuffers.forEach(el -> {el.Destroy();}); + displayCommandBuffers.clear(); + displayCommandBuffers = null; + } + + if (null != hittestCommandBuffers) { + hittestCommandBuffers.forEach(el -> {el.Destroy();}); + hittestCommandBuffers.clear(); + hittestCommandBuffers = null; + } + } + + + // ========================> Descriptors <======================== \\ + + private int CreateDescriptorLayout() { + int ret; + + try (MemoryStack stack = stackPush()) { + VkDescriptorSetLayoutBinding.Buffer bindings = VkDescriptorSetLayoutBinding.callocStack(4, stack); + + // 0: Vertex uniform buffer + VkDescriptorSetLayoutBinding vertexUBDSLB = bindings.get(0); + vertexUBDSLB.binding(0); + vertexUBDSLB.descriptorCount(1); + vertexUBDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + vertexUBDSLB.pImmutableSamplers(null); + vertexUBDSLB.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + + // 1: Vertex samplerBuffer (position buffer) + VkDescriptorSetLayoutBinding vertexSamplerDSLB = bindings.get(1); + vertexSamplerDSLB.binding(1); + vertexSamplerDSLB.descriptorCount(1); + vertexSamplerDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + vertexSamplerDSLB.pImmutableSamplers(null); + vertexSamplerDSLB.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + + // 2: Geometry uniform buffer + VkDescriptorSetLayoutBinding geometryUBDSLB = bindings.get(2); + geometryUBDSLB.binding(2); + geometryUBDSLB.descriptorCount(1); + geometryUBDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + geometryUBDSLB.pImmutableSamplers(null); + geometryUBDSLB.stageFlags(VK_SHADER_STAGE_GEOMETRY_BIT); + + // 3: Fragment sampler2Darray (atlas) + VkDescriptorSetLayoutBinding fragmentSamplerDSLB = bindings.get(3); + fragmentSamplerDSLB.binding(3); + fragmentSamplerDSLB.descriptorCount(1); + fragmentSamplerDSLB.descriptorType(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + fragmentSamplerDSLB.pImmutableSamplers(null); + fragmentSamplerDSLB.stageFlags(VK_SHADER_STAGE_FRAGMENT_BIT); + + + VkDescriptorSetLayoutCreateInfo layoutInfo = VkDescriptorSetLayoutCreateInfo.callocStack(stack); + layoutInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO); + layoutInfo.pBindings(bindings); + + LongBuffer pDescriptorSetLayout = stack.mallocLong(1); + + ret = vkCreateDescriptorSetLayout(CVKDevice.GetVkDevice(), layoutInfo, null, pDescriptorSetLayout); + if (VkSucceeded(ret)) { + hDescriptorLayout = pDescriptorSetLayout.get(0); + GetLogger().info("CVKLoopsRenderable created hDescriptorLayout: 0x%016X", hDescriptorLayout); + } + } + return ret; + } + + private void DestroyDescriptorLayout() { + GetLogger().info("CVKLoopsRenderable destroying hDescriptorLayout: 0x%016X", hDescriptorLayout); + vkDestroyDescriptorSetLayout(CVKDevice.GetVkDevice(), hDescriptorLayout, null); + hDescriptorLayout = VK_NULL_HANDLE; + } + + private int CreateDescriptorSets(MemoryStack stack) { + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + // The same layout is used for each descriptor set. Each image has a + // an identical copy of the descriptor set so allow the GPU and CPU to + // desynchronise (when there are 2 or more images in the swapchain). + final int imageCount = cvkSwapChain.GetImageCount(); + LongBuffer layouts = stack.mallocLong(imageCount); + for (int i = 0; i < imageCount; ++i) { + layouts.put(i, hDescriptorLayout); + } + + VkDescriptorSetAllocateInfo allocInfo = VkDescriptorSetAllocateInfo.callocStack(stack); + allocInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO); + allocInfo.descriptorPool(cvkDescriptorPool.GetDescriptorPoolHandle()); + allocInfo.pSetLayouts(layouts); + + // Allocate the descriptor sets from the descriptor pool, they'll be unitialised + pDescriptorSets = MemoryUtil.memAllocLong(imageCount); + ret = vkAllocateDescriptorSets(CVKDevice.GetVkDevice(), allocInfo, pDescriptorSets); + if (VkFailed(ret)) { return ret; } + + for (int i = 0; i < pDescriptorSets.capacity(); ++i) { + GetLogger().info("CVKLoopsRenderable allocated hDescriptorSet %d: 0x%016X", i, pDescriptorSets.get(i)); + } + + return UpdateDescriptorSets(stack); + } + + // TODO: do we gain anything by having buffered UBOs? + private int UpdateDescriptorSets(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(pDescriptorSets); + CVKAssert(pDescriptorSets.capacity() > 0); + CVKAssertNotNull(vertexUniformBuffers); + CVKAssert(vertexUniformBuffers.size() > 0); + CVKAssertNotNull(geometryUniformBuffers); + CVKAssert(geometryUniformBuffers.size() > 0); + + int ret = VK_SUCCESS; + + final int imageCount = cvkSwapChain.GetImageCount(); + + final long positionBufferSize = cvkVisualProcessor.GetPositionBufferSize(); + hPositionBuffer = cvkVisualProcessor.GetPositionBufferHandle(); + hPositionBufferView = cvkVisualProcessor.GetPositionBufferViewHandle(); + hIconAtlasSampler = CVKIconTextureAtlas.GetInstance().GetAtlasSamplerHandle(); + hIconAtlasImageView = CVKIconTextureAtlas.GetInstance().GetAtlasImageViewHandle(); + CVKAssertNotNull(hPositionBuffer); + CVKAssertNotNull(hPositionBufferView); + CVKAssertNotNull(hIconAtlasSampler); + CVKAssertNotNull(hIconAtlasImageView); + + // - Descriptor info structs - + // We create these to describe the different resources we want to address + // in shaders. We have one info struct per resource. We then create a + // write descriptor set structure for each resource for each image. For + // buffered resources like the the uniform buffers we wait to set the + // buffer resource until the image loop below. + + // Struct for the uniform buffer used by Loop.vs + VkDescriptorBufferInfo.Buffer vertexUniformBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + // vertexUniformBufferInfo.buffer is set per imageIndex + vertexUniformBufferInfo.offset(0); + vertexUniformBufferInfo.range(VertexUniformBufferObject.SizeOf()); + + // Struct for texel buffer (positions) used by Loop.vs + VkDescriptorBufferInfo.Buffer positionsTexelBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + positionsTexelBufferInfo.buffer(hPositionBuffer); + positionsTexelBufferInfo.offset(0); + positionsTexelBufferInfo.range(positionBufferSize); + + // Struct for the uniform buffer used by Loop.gs + VkDescriptorBufferInfo.Buffer geometryUniformBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + // geometryBufferInfo.buffer is set per imageIndex + geometryUniformBufferInfo.offset(0); + geometryUniformBufferInfo.range(GeometryUniformBufferObject.SizeOf()); + + // Struct for the size of the image sampler (atlas) used by Loop.fs + VkDescriptorImageInfo.Buffer imageInfo = VkDescriptorImageInfo.callocStack(1, stack); + imageInfo.imageLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + imageInfo.imageView(hIconAtlasImageView); + imageInfo.sampler(hIconAtlasSampler); + + // We need 4 write descriptors, 2 for uniform buffers, 1 for texel buffers and 1 for the texture sampler + VkWriteDescriptorSet.Buffer descriptorWrites = VkWriteDescriptorSet.callocStack(4, stack); + + // Vertex uniform buffer + VkWriteDescriptorSet vertexUBDescriptorWrite = descriptorWrites.get(0); + vertexUBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + vertexUBDescriptorWrite.dstBinding(0); + vertexUBDescriptorWrite.dstArrayElement(0); + vertexUBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + vertexUBDescriptorWrite.descriptorCount(1); + vertexUBDescriptorWrite.pBufferInfo(vertexUniformBufferInfo); + + // Vertex texel buffer (positions) + VkWriteDescriptorSet positionsTBDescriptorWrite = descriptorWrites.get(1); + positionsTBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + positionsTBDescriptorWrite.dstBinding(1); + positionsTBDescriptorWrite.dstArrayElement(0); + positionsTBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + positionsTBDescriptorWrite.descriptorCount(1); + positionsTBDescriptorWrite.pBufferInfo(positionsTexelBufferInfo); + positionsTBDescriptorWrite.pTexelBufferView(stack.longs(hPositionBufferView)); + + // Geometry uniform buffer + VkWriteDescriptorSet geometryUBDescriptorWrite = descriptorWrites.get(2); + geometryUBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + geometryUBDescriptorWrite.dstBinding(2); + geometryUBDescriptorWrite.dstArrayElement(0); + geometryUBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + geometryUBDescriptorWrite.descriptorCount(1); + geometryUBDescriptorWrite.pBufferInfo(geometryUniformBufferInfo); + + // Fragment image (atlas) sampler + VkWriteDescriptorSet atlasSamplerDescriptorWrite = descriptorWrites.get(3); + atlasSamplerDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + atlasSamplerDescriptorWrite.dstBinding(3); + atlasSamplerDescriptorWrite.dstArrayElement(0); + atlasSamplerDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); + atlasSamplerDescriptorWrite.descriptorCount(1); + atlasSamplerDescriptorWrite.pImageInfo(imageInfo); + + for (int i = 0; i < imageCount; ++i) { + // Update the buffered resource buffers + vertexUniformBufferInfo.buffer(vertexUniformBuffers.get(i).GetBufferHandle()); + geometryUniformBufferInfo.buffer(geometryUniformBuffers.get(i).GetBufferHandle()); + + // Set the descriptor set we're updating in each write struct + long descriptorSet = pDescriptorSets.get(i); + descriptorWrites.forEach(el -> {el.dstSet(descriptorSet);}); + + // Update the descriptors with a write and no copy + GetLogger().info("CVKLoopsRenderable updating descriptorSet: 0x%016X", descriptorSet); + vkUpdateDescriptorSets(CVKDevice.GetVkDevice(), descriptorWrites, null); + } + + SetDescriptorSetsState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private int DestroyDescriptorSets() { + int ret = VK_SUCCESS; + + if (pDescriptorSets != null) { + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(cvkDescriptorPool.GetDescriptorPoolHandle()); + GetLogger().fine("CVKLoopsRenderable returning %d descriptor sets to the pool", pDescriptorSets.capacity()); + + for (int i = 0; i < pDescriptorSets.capacity(); ++i) { + GetLogger().info("CVKLoopsRenderable freeing hDescriptorSet %d: 0x%016X", i, pDescriptorSets.get(i)); + } + + // After calling vkFreeDescriptorSets, all descriptor sets in pDescriptorSets are invalid. + ret = vkFreeDescriptorSets(CVKDevice.GetVkDevice(), cvkDescriptorPool.GetDescriptorPoolHandle(), pDescriptorSets); + pDescriptorSets = null; + checkVKret(ret); + } + + return ret; + } + + @Override + public int DestroyDescriptorPoolResources() { + int ret = VK_SUCCESS; + + if (cvkDescriptorPool != null) { + return DestroyDescriptorSets(); + } + + return ret; + } + + @Override + public void IncrementDescriptorTypeRequirements(CVKDescriptorPoolRequirements reqs, CVKDescriptorPoolRequirements perImageReqs) { + // Loop.vs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]; + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER]; + + // Loop.gs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]; + + // Loop.fs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER]; + + // One set per image + ++perImageReqs.poolDesciptorSetCount; + } + + @Override + public int SetNewDescriptorPool(CVKDescriptorPool newDescriptorPool) { + int ret = super.SetNewDescriptorPool(newDescriptorPool); + if (VkFailed(ret)) { return ret; } + SetDescriptorSetsState(CVK_RESOURCE_NEEDS_REBUILD); + return ret; + } + + + // ========================> Pipelines <======================== \\ + + private int CreatePipelineLayout() { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(hDescriptorLayout); + + int ret; + try (MemoryStack stack = stackPush()) { + VkPushConstantRange.Buffer pushConstantRange; + pushConstantRange = VkPushConstantRange.calloc(2); + pushConstantRange.get(0).stageFlags(MODEL_VIEW_PUSH_CONSTANT_STAGES); + pushConstantRange.get(0).size(MODEL_VIEW_PUSH_CONSTANT_SIZE); + pushConstantRange.get(0).offset(0); + + pushConstantRange.get(1).stageFlags(HIT_TEST_PUSH_CONSTANT_STAGES); + pushConstantRange.get(1).size(HIT_TEST_PUSH_CONSTANT_SIZE); + pushConstantRange.get(1).offset(MODEL_VIEW_PUSH_CONSTANT_SIZE); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = VkPipelineLayoutCreateInfo.callocStack(stack); + pipelineLayoutInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO); + pipelineLayoutInfo.pSetLayouts(stack.longs(hDescriptorLayout)); + pipelineLayoutInfo.pPushConstantRanges(pushConstantRange); + LongBuffer pPipelineLayout = stack.longs(VK_NULL_HANDLE); + ret = vkCreatePipelineLayout(CVKDevice.GetVkDevice(), pipelineLayoutInfo, null, pPipelineLayout); + if (VkFailed(ret)) { return ret; } + hPipelineLayout = pPipelineLayout.get(0); + CVKAssert(hPipelineLayout != VK_NULL_HANDLE); + } + return ret; + } + + private void DestroyPipelineLayout() { + if (hPipelineLayout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(CVKDevice.GetVkDevice(), hPipelineLayout, null); + hPipelineLayout = VK_NULL_HANDLE; + } + } + + + // ========================> Display <======================== \\ + + @Override + public boolean NeedsDisplayUpdate() { + // Race condition: icons owns the position buffer and the update task (on + // the visual processor thread) may not yet be complete. If this happens + // before the first update is finished icons will have a vertexCount of + // 0 so it won't be able to create the position buffer yet. If we can't + // get a handle to the current position buffer then we just skip updating + // until we can. + if (cvkVisualProcessor.GetPositionBufferHandle() == VK_NULL_HANDLE) { + return false; + } + + if (hPositionBuffer != cvkVisualProcessor.GetPositionBufferHandle() || + hPositionBufferView != cvkVisualProcessor.GetPositionBufferViewHandle() || + hIconAtlasSampler != CVKIconTextureAtlas.GetInstance().GetAtlasSamplerHandle() || + hIconAtlasImageView != CVKIconTextureAtlas.GetInstance().GetAtlasImageViewHandle()) { + if (descriptorSetsState != CVK_RESOURCE_NEEDS_REBUILD) { + descriptorSetsState = CVK_RESOURCE_NEEDS_UPDATE; + } + } + + return vertexCount > 0 && + (vertexUniformBufferState != CVK_RESOURCE_CLEAN || + geometryUniformBufferState != CVK_RESOURCE_CLEAN || + vertexBuffersState != CVK_RESOURCE_CLEAN || + commandBuffersState != CVK_RESOURCE_CLEAN || + descriptorSetsState != CVK_RESOURCE_CLEAN || + pipelinesState != CVK_RESOURCE_CLEAN); + } + + @Override + public int DisplayUpdate() { + int ret = VK_SUCCESS; + cvkVisualProcessor.VerifyInRenderThread(); + + try (MemoryStack stack = stackPush()) { + // Update vertex buffers + if (vertexBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + DestroyVertexBuffers(); + ret = CreateVertexBuffers(); + if (VkFailed(ret)) { return ret; } + } else if (vertexBuffersState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateVertexBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Vertex uniform buffer + if (vertexUniformBufferState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateVertexUniformBuffers(); + if (VkFailed(ret)) { return ret; } + } else if (vertexUniformBufferState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateVertexUniformBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Geometry uniform buffer + if (geometryUniformBufferState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateGeometryUniformBuffers(); + if (VkFailed(ret)) { return ret; } + } else if (geometryUniformBufferState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateGeometryUniformBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Descriptors (binding values to shaders parameters) + if (descriptorSetsState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateDescriptorSets(stack); + if (VkFailed(ret)) { return ret; } + } else if (descriptorSetsState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateDescriptorSets(stack); + if (VkFailed(ret)) { return ret; } + } + + // Command buffers (rendering commands enqueued on the GPU) + if (commandBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateCommandBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Pipelines (all the render state and resources in one object) + if (pipelinesState == CVK_RESOURCE_NEEDS_REBUILD) { + displayPipelines = new ArrayList<>(cvkSwapChain.GetImageCount()); + ret = CreatePipelines(cvkSwapChain.GetRenderPassHandle(), displayPipelines); + if (VkFailed(ret)) { return ret; } + + hitTestPipelines = new ArrayList<>(cvkSwapChain.GetImageCount()); + ret = CreatePipelines(cvkSwapChain.GetOffscreenRenderPassHandle(), hitTestPipelines); + if (VkFailed(ret)) { return ret; } + } + } + + return ret; + } + + + // ========================> Tasks <======================== \\ + // NOTE: we effectively have two levels of staging. The second level is pretty + // straightforward, we need the resources we render with to be in the most + // optimal memory possible: resident GPU memory. This memory cannot be written + // by the CPU so we can't update it directly, instead we update staging buffers + // that are both GPU and CPU writable then copy from that into the final GPU + // buffers. + // The first level of staging is required as our staging buffers are VkDevice + // resources and the device may not be initialised when these tasks are called. + // This is the case when a graph is loaded into a new tab, we need to be able + // to process the tasks that load the graph vertices etc before the device is + // ready to create staging buffers. For this reason we update local arrays + // in the BuildArray functions, then copy these to our staging buffers + // during the renderer's display loop (when the rendering lambda of each task + // is executed). This also means we don't need to synchronise these arrays + // created by the visual processor's thread with the staging buffers that have + // a lifespan entirely within the rendering thread (AWT event thread). This + // is possible because the arrays are locals in the task functions and aren't + // modified by the visual processor thread after they've been added to the + // queue of tasks for the renderer to process. + + + /** + * + * @param access: the view of this graph + * @param loopedConnections: list of node vertices that have looped connections + * @return: an array of vertex objects ready to copy into the VB + */ + private Vertex[] BuildVertexArray(final VisualAccess access, List loopedConnections) { + Vertex[] vertices = null; + if (!loopedConnections.isEmpty()) { + vertices = new Vertex[loopedConnections.size()]; + for (int i = 0; i < loopedConnections.size(); ++i) { + int pos = loopedConnections.get(i); + final ConstellationColor colour = access.getConnectionColor(pos); + final float visibility = access.getConnectionVisibility(pos); + final int connectionID = access.getConnectionId(pos); + final int vertexID = access.getConnectionLowVertex(pos); + final int flags = (access.getConnectionDimmed(pos) ? 2 : 0) | (access.getConnectionSelected(pos) ? 1 : 0); + final int iconID = access.getConnectionDirected(pos) ? CVKIconTextureAtlas.LOOP_DIRECTED_ICON_INDEX : CVKIconTextureAtlas.LOOP_UNDIRECTED_ICON_INDEX; + vertices[i] = new Vertex(colour, + visibility, + connectionID, + vertexID, + flags, + iconID); + } + } + + return vertices; + } + + private void RebuildVertexStagingBuffer(Vertex[] vertices) { + vertexCount = (vertices != null ? vertices.length : 0); + final int newSizeBytes = vertexCount * Vertex.BYTES; + final boolean recreate = cvkVertexStagingBuffer == null || newSizeBytes != cvkVertexStagingBuffer.GetBufferSize(); + + if (recreate) { + if (cvkVertexStagingBuffer != null) { + cvkVertexStagingBuffer.Destroy(); + cvkVertexStagingBuffer = null; + } + + if (newSizeBytes > 0) { + cvkVertexStagingBuffer = CVKBuffer.Create(newSizeBytes, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKLoopsRenderable.RebuildVertexStagingBuffer cvkVertexStagingBuffer"); + } + } + + if (newSizeBytes > 0) { + UpdateVertexStagingBuffer(vertices, 0, vertices.length - 1); + } + } + + private void UpdateVertexStagingBuffer(Vertex[] vertices, int first, int last) { + CVKAssertNotNull(cvkVertexStagingBuffer); + CVKAssertNotNull(vertices != null); + CVKAssert(vertices.length > 0 && vertices.length > (last - first)); + CVKAssert(last >= 0 && last >= first && first >= 0); + + int offset = first * Vertex.BYTES; + int size = ((last - first) + 1) * Vertex.BYTES; + + ByteBuffer pMemory = cvkVertexStagingBuffer.StartMemoryMap(offset, size); + for (Vertex vertex : vertices) { + vertex.CopyToSequentially(pMemory); + } + cvkVertexStagingBuffer.EndMemoryMap(); + pMemory = null; // now unmapped, do not use + } + + public CVKRenderUpdateTask TaskUpdateLoops(final VisualChange change, final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + + // Loops aren't separate data, they're just connections where both vertices are the same + final int changedVerticeRange[] = change.getRange(); + List loopedConnections = new ArrayList<>(); + for (int i = 0; i < access.getConnectionCount(); i++) { + if (access.getConnectionLowVertex(i) == access.getConnectionHighVertex(i)) { + if (changedVerticeRange == null || (i >= changedVerticeRange[0] && i <= changedVerticeRange[1])) { + loopedConnections.add(i); + } + } + } + + GetLogger().fine("TaskUpdateLoops frame %d: %d loops", cvkVisualProcessor.GetFrameNumber(), loopedConnections.size()); + final boolean rebuildRequired = cvkVertexStagingBuffer == null || + loopedConnections.size() * Vertex.BYTES != cvkVertexStagingBuffer.GetBufferSize() || + change.isEmpty(); + + final Vertex vertexArray[]; + vertexArray = BuildVertexArray(access, loopedConnections); + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + if (rebuildRequired) { + RebuildVertexStagingBuffer(vertexArray); + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + vertexCount = vertexArray != null ? vertexArray.length : 0; + } else if (vertexBuffersState != CVK_RESOURCE_NEEDS_REBUILD) { + UpdateVertexStagingBuffer(vertexArray, changedVerticeRange[0], changedVerticeRange[1]); + SetVertexBuffersState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + public CVKRenderUpdateTask TaskUpdateCamera() { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + UpdateVertexPushConstants(); + }; + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKNewLineRenderable.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKNewLineRenderable.java new file mode 100644 index 0000000000..336cf2f345 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKNewLineRenderable.java @@ -0,0 +1,540 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.renderables; + +import au.gov.asd.tac.constellation.utilities.camera.Camera; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.*; +import static org.lwjgl.system.MemoryUtil.*; +import static org.lwjgl.vulkan.VK10.*; +import static org.lwjgl.system.MemoryStack.stackPush; +import au.gov.asd.tac.constellation.utilities.graphics.Matrix44f; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import java.nio.ByteBuffer; +import java.nio.LongBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkPipelineLayoutCreateInfo; +import org.lwjgl.vulkan.VkCommandBufferInheritanceInfo; +import org.lwjgl.vulkan.VkVertexInputAttributeDescription; +import org.lwjgl.vulkan.VkVertexInputBindingDescription; +import au.gov.asd.tac.constellation.utilities.graphics.Vector3f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector4f; +import au.gov.asd.tac.constellation.utilities.visual.NewLineModel; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool.CVKDescriptorPoolRequirements; +import au.gov.asd.tac.constellation.visual.vulkan.CVKVisualProcessor; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_CLEAN; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_NEEDS_REBUILD; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_NEEDS_UPDATE; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKBuffer; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKCommandBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.LinkedBlockingDeque; +import org.lwjgl.PointerBuffer; +import org.lwjgl.vulkan.VkPushConstantRange; + + +/******************************************************************************* + * CVKNewLineRenderable + * + * This class renders the indicator line when the user has selected a node in + * the add connection mode. It is not the line for the connection (which uses + * different shaders). There is only ever one indicator line. This renderable + * is very similar to CVKAxesRenderable in that is draws its line in screen + * space and uses the same shaders. + * + * This is the equivalent of au.gov.asd.tac.constellation.graph.interaction. + * visual.renderables.NewLineRenderable in the JOGL display version. + *******************************************************************************/ + +public class CVKNewLineRenderable extends CVKRenderable { + // From CoreInteractiveGraph\src\au\gov\asd\tac\constellation\graph\interaction\visual\renderables\NewLineRenderable.java + public static final int NEW_LINE_WIDTH = 2; + public static final Vector4f NEW_LINE_COLOR = new Vector4f(1.0f, 1.0f, 1.0f, 1.0f); + private static final int NUMBER_OF_VERTICES = 2; + + // Copied from NewLineRenderable this is the mechanism for synchronising input + // and rendering threads. Only the last 'model' is used. + private NewLineModel model = null; + private final BlockingDeque modelQueue = new LinkedBlockingDeque<>(); + + private final Vertex[] vertices = new Vertex[NUMBER_OF_VERTICES]; + private CVKBuffer cvkStagingBuffer = null; + private CVKBuffer cvkVertexBuffer = null; + private List displayCommandBuffers = null; + private ByteBuffer pushConstants = null; + + + // ========================> Classes <======================== \\ + + private static class Vertex { + + private static final int BYTES = Vector3f.BYTES + Vector4f.BYTES; + private static final int OFFSETOF_POS = 0; + private static final int OFFSETOF_COLOR = Vector3f.BYTES; + + private final Vector3f vertex; + private final Vector4f color; + + public Vertex(final Vector3f vertex, final Vector4f color) { + this.vertex = vertex; + this.color = color; + } + + private static void CopyTo(ByteBuffer buffer, Vertex[] vertices) { + for(Vertex vertex : vertices) { + buffer.putFloat(vertex.vertex.getX()); + buffer.putFloat(vertex.vertex.getY()); + buffer.putFloat(vertex.vertex.getZ()); + + buffer.putFloat(vertex.color.a[0]); + buffer.putFloat(vertex.color.a[1]); + buffer.putFloat(vertex.color.a[2]); + buffer.putFloat(vertex.color.a[3]); + } + } + + private static VkVertexInputBindingDescription.Buffer GetBindingDescription() { + + VkVertexInputBindingDescription.Buffer bindingDescription = + VkVertexInputBindingDescription.callocStack(1); + + bindingDescription.binding(0); + bindingDescription.stride(Vertex.BYTES); + bindingDescription.inputRate(VK_VERTEX_INPUT_RATE_VERTEX); + + return bindingDescription; + } + + private static VkVertexInputAttributeDescription.Buffer GetAttributeDescriptions() { + + VkVertexInputAttributeDescription.Buffer attributeDescriptions = + VkVertexInputAttributeDescription.callocStack(2); + + // Vertex + VkVertexInputAttributeDescription vertexDescription = attributeDescriptions.get(0); + vertexDescription.binding(0); + vertexDescription.location(0); + vertexDescription.format(VK_FORMAT_R32G32B32_SFLOAT); + vertexDescription.offset(OFFSETOF_POS); + + // Color + VkVertexInputAttributeDescription colorDescription = attributeDescriptions.get(1); + colorDescription.binding(0); + colorDescription.location(1); + colorDescription.format(VK_FORMAT_R32G32B32A32_SFLOAT); + colorDescription.offset(OFFSETOF_COLOR); + + return attributeDescriptions.rewind(); + } + } + + @Override + protected VkVertexInputBindingDescription.Buffer GetVertexBindingDescription() { + return Vertex.GetBindingDescription(); + } + + @Override + protected VkVertexInputAttributeDescription.Buffer GetVertexAttributeDescriptions() { + return Vertex.GetAttributeDescriptions(); + } + + + // ========================> Shaders <======================== \\ + + @Override + protected String GetVertexShaderName() { return "PassThru.vs"; } + + @Override + protected String GetGeometryShaderName() { return "PassThruLine.gs"; } + + @Override + protected String GetFragmentShaderName() { return "PassThru.fs"; } + + + // ========================> Lifetime <======================== \\ + + public CVKNewLineRenderable(CVKVisualProcessor visualProcessor) { + super(visualProcessor); + colourBlend = true; + depthTest = true; + depthWrite = true; + depthCompareOperation = VK_COMPARE_OP_ALWAYS; + assemblyTopology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + colourWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT; + } + + @Override + public int Initialise() { + // Do this here rather than ctor as the CVKDevice won't be initialised + // during the ctor call. + if (CVKDevice.AreVkLogicOpsSupported()) { + logicOpEnable = true; + logicOp = VK_LOGIC_OP_INVERT; + } + + int ret = super.Initialise(); + if (VkFailed(ret)) { return ret; } + + // Initialise push constants to identity mtx + CreatePushConstants(); + + ret = CreatePipelineLayout(); + if (VkFailed(ret)) { return ret; } + + return VK_SUCCESS; + } + + @Override + public void Destroy() { + DestroyCommandBuffers(); + DestroyVertexBuffer(); + DestroyPipelines(); + DestroyPipelineLayout(); + DestroyPushConstants(); + + CVKAssertNull(displayPipelines); + CVKAssertNull(hPipelineLayout); + CVKAssertNull(cvkVertexBuffer); + CVKAssertNull(displayCommandBuffers); + CVKAssertNull(pushConstants); + } + + + // ========================> Swap chain <======================== \\ + + @Override + protected int DestroySwapChainResources(){ + cvkVisualProcessor.VerifyInRenderThread(); + int ret = VK_SUCCESS; + + // We only need to recreate these resources if the number of images in + // the swapchain changes or if this is the first call after the initial + // swapchain is created. + if (displayPipelines != null && swapChainImageCountChanged) { + DestroyVertexBuffer(); + DestroyCommandBuffers(); + DestroyPipelines(); + + CVKAssertNull(displayPipelines); + CVKAssertNull(cvkVertexBuffer); + CVKAssertNull(displayCommandBuffers); + } + + return ret; + } + + + // ========================> Vertex buffers <======================== \\ + + private int CreateVertexBuffer(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + + // Allocate the vertex objects + vertices[0] = new Vertex(ZERO_3F, NEW_LINE_COLOR); + vertices[1] = new Vertex(ZERO_3F, NEW_LINE_COLOR); + + // Staging buffer so our VB can be device local (most performant memory) + final int size = vertices.length * Vertex.BYTES; + cvkStagingBuffer = CVKBuffer.Create(size, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKNewLineRenderable.CreateVertexBuffer cvkStagingBuffer"); + + // Create the actual VB which will be device local + cvkVertexBuffer = CVKBuffer.Create(size, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + "CVKNewLineRenderable.CreateVertexBuffers cvkStagingBuffer"); + cvkVertexBuffer.CopyFrom(cvkStagingBuffer); + + return UpdateVertexBuffer(stack); + } + + private int UpdateVertexBuffer(MemoryStack stack) { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(cvkStagingBuffer); + CVKAssertNotNull(cvkStagingBuffer.GetMemoryBufferHandle()); + + int ret = VK_SUCCESS; + + final Camera camera = cvkVisualProcessor.getDisplayCamera(); + NewLineModel updatedModel = modelQueue.peek(); + + // Logic copied from NewLineRenderable. Looks like it is purging the + // queue of any models with a different camera to the current one. + while (updatedModel != null && updatedModel.getCamera() != camera) { + modelQueue.remove(); + updatedModel = modelQueue.peek(); + } + + // This looks like it is finding the last (most recent) model that matches + // the current camera, adding it back to the queue after removing all other + // model. + if (updatedModel != null) { + updatedModel = modelQueue.remove(); + NewLineModel nextModel = modelQueue.peek(); + while (nextModel != null && nextModel.getCamera() == camera) { + updatedModel = modelQueue.remove(); + nextModel = modelQueue.peek(); + } + modelQueue.addFirst(updatedModel); + } + model = updatedModel; + + if (model != null && !model.isClear()) { + // Update our vertex objects. Note there was a weird bug when instead + // of setting a vertex to a new object vertices[n].vertex.set() was + // used. The result would move the axes renderable. I didn't have + // time to investigate but there could be something hokey going with + // memory on the Java heap? + vertices[0] = new Vertex(model.getStartLocation(), NEW_LINE_COLOR); + vertices[1] = new Vertex(model.getEndLocation(), NEW_LINE_COLOR); + // BROKEN. Hopefully someone more knowledgable about Java might be + // able to spot the problem. +// vertices[0].vertex.set(model.getStartLocation()); +// vertices[1].vertex.set(model.getEndLocation()); + + // Update the staging buffer + final int size = vertices.length * Vertex.BYTES; + PointerBuffer data = stack.mallocPointer(1); + vkMapMemory(CVKDevice.GetVkDevice(), cvkStagingBuffer.GetMemoryBufferHandle(), 0, size, 0, data); + if (VkFailed(ret)) { return ret; } + { + Vertex.CopyTo(data.getByteBuffer(0, size), vertices); + } + vkUnmapMemory(CVKDevice.GetVkDevice(), cvkStagingBuffer.GetMemoryBufferHandle()); + + // Update the vertex buffer + cvkVertexBuffer.CopyFrom(cvkStagingBuffer); + } + + SetVertexBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public int GetVertexCount() { + if (model != null && !model.isClear()) { + return NUMBER_OF_VERTICES; + } else { + return 0; + } + } + + private void DestroyVertexBuffer() { + if (null != cvkVertexBuffer) { + cvkVertexBuffer.Destroy(); + cvkVertexBuffer = null; + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + } + } + + + // ========================> Push constants <======================== \\ + + private int CreatePushConstants() { + // Initialise push constants to identity mtx + pushConstants = memAlloc(Matrix44f.BYTES); + PutMatrix44f(pushConstants, IDENTITY_44F); + pushConstants.flip(); + + return VK_SUCCESS; + } + + private void UpdatePushConstants(){ + CVKAssertNotNull(cvkSwapChain); + + PutMatrix44f(pushConstants, cvkVisualProcessor.getDisplayModelViewProjectionMatrix()); + pushConstants.flip(); + } + + private void DestroyPushConstants() { + if (pushConstants != null) { + memFree(pushConstants); + pushConstants = null; + } + } + + + // ========================> Command buffers <======================== \\ + + public int CreateCommandBuffers() { + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + int imageCount = cvkSwapChain.GetImageCount(); + + displayCommandBuffers = new ArrayList<>(imageCount); + + for (int i = 0; i < imageCount; ++i) { + CVKCommandBuffer buffer = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_SECONDARY, + GetLogger(), + String.format("CVKNewLineRenderable %d", i)); + displayCommandBuffers.add(buffer); + } + + SetCommandBuffersState(CVK_RESOURCE_CLEAN); + + GetLogger().info("Init Command Buffer - CVKNewLineRenderable"); + + return ret; + } + + @Override + public VkCommandBuffer GetDisplayCommandBuffer(int imageIndex) { + return displayCommandBuffers.get(imageIndex).GetVKCommandBuffer(); + } + + @Override + public int RecordDisplayCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int imageIndex) { + CVKAssertNotNull(cvkSwapChain); + cvkVisualProcessor.VerifyInRenderThread(); + int ret = VK_SUCCESS; + + CVKCommandBuffer commandBuffer = displayCommandBuffers.get(imageIndex); + CVKAssertNotNull(commandBuffer); + CVKAssertNotNull(displayPipelines.get(imageIndex)); + + ret = commandBuffer.BeginRecordSecondary(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, + inheritanceInfo); + if (VkFailed(ret)) { return ret; } + + commandBuffer.SetViewPort(cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight()); + commandBuffer.SetScissor(cvkVisualProcessor.GetCanvas().GetCurrentSurfaceExtent()); + commandBuffer.BindGraphicsPipeline(displayPipelines.get(imageIndex)); + commandBuffer.BindVertexInput(cvkVertexBuffer.GetBufferHandle()); + commandBuffer.PushConstants(hPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, pushConstants); + + commandBuffer.Draw(GetVertexCount()); + + ret = commandBuffer.FinishRecord(); + if (VkFailed(ret)) { return ret; } + + return ret; + } + + private void DestroyCommandBuffers() { + if (null != displayCommandBuffers && displayCommandBuffers.size() > 0) { + displayCommandBuffers.forEach(el -> {el.Destroy();}); + displayCommandBuffers.clear(); + displayCommandBuffers = null; + SetCommandBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + } + } + + + // ========================> Descriptors <======================== \\ + + @Override + public void IncrementDescriptorTypeRequirements(CVKDescriptorPoolRequirements reqs, CVKDescriptorPoolRequirements perImageReqs) { + // No descriptor sets required because this class uses push constants instead of descriptor bound uniform buffers. + } + + @Override + public int DestroyDescriptorPoolResources() { + return VK_SUCCESS; + } + + + // ========================> Pipelines <======================== \\ + + private int CreatePipelineLayout() { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + + int ret; + try (MemoryStack stack = stackPush()) { + VkPushConstantRange.Buffer pushConstantRange; + pushConstantRange = VkPushConstantRange.callocStack(1, stack); + pushConstantRange.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + pushConstantRange.size(Matrix44f.BYTES); + pushConstantRange.offset(0); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = VkPipelineLayoutCreateInfo.callocStack(stack); + pipelineLayoutInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO); + pipelineLayoutInfo.pPushConstantRanges(pushConstantRange); + LongBuffer pPipelineLayout = stack.longs(VK_NULL_HANDLE); + ret = vkCreatePipelineLayout(CVKDevice.GetVkDevice(), pipelineLayoutInfo, null, pPipelineLayout); + if (VkFailed(ret)) { return ret; } + hPipelineLayout = pPipelineLayout.get(0); + CVKAssertNotNull(hPipelineLayout); + } + return ret; + } + + private void DestroyPipelineLayout() { + if (hPipelineLayout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(CVKDevice.GetVkDevice(), hPipelineLayout, null); + hPipelineLayout = VK_NULL_HANDLE; + } + } + + + // ========================> Display <======================== \\ + + @Override + public boolean NeedsDisplayUpdate() { + return modelQueue.size() > 0; + } + + @Override + public int DisplayUpdate() { + cvkVisualProcessor.VerifyInRenderThread(); + + int ret = VK_SUCCESS; + + try (MemoryStack stack = stackPush()) { + + // We always update vertex buffers while we have a model in the queue + // as that updates the start and end points. CreateVertexBuffer + // calls UpdateVertexBuffer + if (vertexBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateVertexBuffer(stack); + if (VkFailed(ret)) { return ret; } + } else { + ret = UpdateVertexBuffer(stack); + if (VkFailed(ret)) { return ret; } + } + + if (commandBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateCommandBuffers(); + if (VkFailed(ret)) { return ret; } + } + + if (pipelinesState == CVK_RESOURCE_NEEDS_REBUILD) { + displayPipelines = new ArrayList<>(cvkSwapChain.GetImageCount()); + ret = CreatePipelines(cvkSwapChain.GetRenderPassHandle(), displayPipelines); + if (VkFailed(ret)) { return ret; } + } + } + + UpdatePushConstants(); + + return ret; + } + + + // ========================> Tasks <======================== \\ + + public void queueModel(final NewLineModel model) { + modelQueue.add(model); + cvkVisualProcessor.RequestRedraw(); + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKPerspectiveLinksRenderable.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKPerspectiveLinksRenderable.java new file mode 100644 index 0000000000..ff5671bbba --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKPerspectiveLinksRenderable.java @@ -0,0 +1,742 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.renderables; + +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool.CVKDescriptorPoolRequirements; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import au.gov.asd.tac.constellation.visual.vulkan.CVKSwapChain; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_CLEAN; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_NEEDS_REBUILD; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_NEEDS_UPDATE; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKCommandBuffer; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssert; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssertNotNull; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssertNull; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkFailed; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkSucceeded; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.checkVKret; +import java.nio.LongBuffer; +import java.util.ArrayList; +import java.util.List; +import org.lwjgl.system.MemoryStack; +import static org.lwjgl.system.MemoryStack.stackPush; +import org.lwjgl.system.MemoryUtil; +import static org.lwjgl.system.MemoryUtil.memFree; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_LEVEL_SECONDARY; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT; +import static org.lwjgl.vulkan.VK10.VK_CULL_MODE_NONE; +import static org.lwjgl.vulkan.VK10.VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; +import static org.lwjgl.vulkan.VK10.VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.VK_PRIMITIVE_TOPOLOGY_LINE_LIST; +import static org.lwjgl.vulkan.VK10.VK_SHADER_STAGE_FRAGMENT_BIT; +import static org.lwjgl.vulkan.VK10.VK_SHADER_STAGE_GEOMETRY_BIT; +import static org.lwjgl.vulkan.VK10.VK_SHADER_STAGE_VERTEX_BIT; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; +import static org.lwjgl.vulkan.VK10.VK_SUCCESS; +import static org.lwjgl.vulkan.VK10.vkAllocateDescriptorSets; +import static org.lwjgl.vulkan.VK10.vkCreateDescriptorSetLayout; +import static org.lwjgl.vulkan.VK10.vkCreatePipelineLayout; +import static org.lwjgl.vulkan.VK10.vkDestroyDescriptorSetLayout; +import static org.lwjgl.vulkan.VK10.vkDestroyPipelineLayout; +import static org.lwjgl.vulkan.VK10.vkFreeDescriptorSets; +import static org.lwjgl.vulkan.VK10.vkUpdateDescriptorSets; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkCommandBufferInheritanceInfo; +import org.lwjgl.vulkan.VkDescriptorBufferInfo; +import org.lwjgl.vulkan.VkDescriptorSetAllocateInfo; +import org.lwjgl.vulkan.VkDescriptorSetLayoutBinding; +import org.lwjgl.vulkan.VkDescriptorSetLayoutCreateInfo; +import org.lwjgl.vulkan.VkPipelineLayoutCreateInfo; +import org.lwjgl.vulkan.VkPushConstantRange; +import org.lwjgl.vulkan.VkVertexInputAttributeDescription; +import org.lwjgl.vulkan.VkVertexInputBindingDescription; +import org.lwjgl.vulkan.VkWriteDescriptorSet; + + +/******************************************************************************* + * CVKPerspectiveLinksRenderable + * + * This class renders links between nodes as triangles. It is paired with + * CVKLinksRenderable which renders links between nodes with lines. This class + * uses triangles in order to achieve perspective and to add direction arrows. + * + * These bonded pair of classes fulfill the same role as au.gov.asd.tac.constellation. + * visual.opengl.renderer.batcher.LineBatcher in the JOGL version. + * + * LineBatcher draws both a line and triangle-line between two nodes. When the + * camera is close only the triangle-line is visible. As the camera pans out + * the triangle-line becomes less visible eventually becoming sub-pixel. The + * line which was previously not visible becomes visible. It's surprisingly + * effective. + * + * The reason this was split into two renderables in the Vulkan display project + * is that CVKRenderables encapsulate a single pipeline which have a single shader + * for each assembly step (vert, geo, frag). Subsequently they only output a single + * primitive type (triangle_strip, line_list, point_list etc). So rather than + * introduce the complexity of multiple display pipelines into the rest of the + * renderables we just share data and have a renderable for each pipeline. + *******************************************************************************/ + +public class CVKPerspectiveLinksRenderable extends CVKRenderable { + // CVKLinks is the master in this master-slave relationship + private final CVKLinksRenderable cvkLinks; + + // Resources recreated with the swap chain (dependent on the image count) + private LongBuffer pDescriptorSets = null; + private List hitTestPipelines = null; + private List displayCommandBuffers = null; + private List hittestCommandBuffers = null; + + // Resources we don't own but use and must track so we know when to update + // our descriptors + private long hPositionBuffer = VK_NULL_HANDLE; + private long hPositionBufferView = VK_NULL_HANDLE; + + // Push constants for shaders contains the MV matrix and drawHitTest int + private static final int MODEL_VIEW_PUSH_CONSTANT_STAGES = VK_SHADER_STAGE_VERTEX_BIT; + private static final int HIT_TEST_PUSH_CONSTANT_STAGES = VK_SHADER_STAGE_GEOMETRY_BIT | VK_SHADER_STAGE_FRAGMENT_BIT; + + + + // ========================> Classes <======================== \\ + + @Override + protected VkVertexInputBindingDescription.Buffer GetVertexBindingDescription() { + return CVKLinksRenderable.Vertex.GetBindingDescription(); + } + + @Override + protected VkVertexInputAttributeDescription.Buffer GetVertexAttributeDescriptions() { + return CVKLinksRenderable.Vertex.GetAttributeDescriptions(); + } + + + // ========================> Shaders <======================== \\ + + @Override + protected String GetVertexShaderName() { return "Line.vs"; } + + @Override + protected String GetGeometryShaderName() { return "Line.gs"; } + + @Override + protected String GetFragmentShaderName() { return "Line.fs"; } + + + // ========================> Lifetime <======================== \\ + + public CVKPerspectiveLinksRenderable(CVKLinksRenderable links) { + super(links.cvkVisualProcessor); + + cvkLinks = links; + + // Assembly + assemblyTopology = VK_PRIMITIVE_TOPOLOGY_LINE_LIST; + } + + @Override + public int Initialise() { + int ret = super.Initialise(); + if (VkFailed(ret)) { return ret; } + + // Check for double initialisation + CVKAssert(hDescriptorLayout == VK_NULL_HANDLE); + + ret = CreateDescriptorLayout(); + if (VkFailed(ret)) { return ret; } + + ret = CreatePipelineLayout(); + if (VkFailed(ret)) { return ret; } + + return ret; + } + + + @Override + public void Destroy() { + DestroyDescriptorSets(); + DestroyDescriptorLayout(); + DestroyPipelines(); + DestroyPipelineLayout(); + DestroyCommandBuffers(); + + CVKAssertNull(pDescriptorSets); + CVKAssertNull(hDescriptorLayout); + CVKAssertNull(displayCommandBuffers); + CVKAssertNull(displayPipelines); + CVKAssertNull(hPipelineLayout); + } + + + // ========================> Swap chain <======================== \\ + + @Override + protected int DestroySwapChainResources() { + this.cvkSwapChain = null; + + // We only need to recreate these resources if the number of images in + // the swapchain changes or if this is the first call after the initial + // swapchain is created. + if (displayPipelines != null && swapChainImageCountChanged) { + DestroyDescriptorSets(); + DestroyCommandBuffers(); + DestroyPipelines(); + } + + return VK_SUCCESS; + } + + @Override + public int SetNewSwapChain(CVKSwapChain newSwapChain) { + int ret = super.SetNewSwapChain(newSwapChain); + if (VkFailed(ret)) { return ret; } + + if (swapChainImageCountChanged) { + // The number of images has changed, we need to rebuild all image + // buffered resources + SetCommandBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + SetDescriptorSetsState(CVK_RESOURCE_NEEDS_REBUILD); + SetPipelinesState(CVK_RESOURCE_NEEDS_REBUILD); + } + + return ret; + } + + + // ========================> Vertex buffers <======================== \\ + + @Override + public int GetVertexCount() { return cvkLinks.GetVertexCount(); } + + + // ========================> Command buffers <======================== \\ + + public int CreateCommandBuffers(){ + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + int imageCount = cvkSwapChain.GetImageCount(); + + displayCommandBuffers = new ArrayList<>(imageCount); + hittestCommandBuffers = new ArrayList<>(imageCount); + + for (int i = 0; i < imageCount; ++i) { + CVKCommandBuffer buffer = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_SECONDARY, GetLogger(), String.format("CVKPerspectiveLinksRenderable Display Command Buffer %d", i)); + displayCommandBuffers.add(buffer); + + CVKCommandBuffer offscreenBuffer = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_SECONDARY, GetLogger(), String.format("CVKPerspectiveLinksRenderable Offscreen Command Buffer %d", i)); + hittestCommandBuffers.add(offscreenBuffer); + } + + SetCommandBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public VkCommandBuffer GetDisplayCommandBuffer(int imageIndex) { + return displayCommandBuffers.get(imageIndex).GetVKCommandBuffer(); + } + + @Override + public VkCommandBuffer GetHitTestCommandBuffer(int imageIndex) { + return hittestCommandBuffers.get(imageIndex).GetVKCommandBuffer(); + } + + @Override + public int RecordDisplayCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int imageIndex){ + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(CVKDevice.GetCommandPoolHandle()); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + CVKCommandBuffer commandBuffer = displayCommandBuffers.get(imageIndex); + CVKAssert(commandBuffer != null); + CVKAssert(displayPipelines.get(imageIndex) != null); + + commandBuffer.BeginRecordSecondary(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, inheritanceInfo); + + commandBuffer.SetViewPort(cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight()); + commandBuffer.SetScissor(cvkVisualProcessor.GetCanvas().GetCurrentSurfaceExtent()); + + commandBuffer.BindGraphicsPipeline(displayPipelines.get(imageIndex)); + commandBuffer.BindVertexInput(cvkLinks.GetVertexBufferHandle(imageIndex)); + + // Push MV matrix to the vertex shader + commandBuffer.PushConstants(hPipelineLayout, + MODEL_VIEW_PUSH_CONSTANT_STAGES, + 0, + cvkLinks.GetModelViewPushConstants()); + + // Push drawHitTest flag to the geometry and fragment shader + commandBuffer.PushConstants(hPipelineLayout, + HIT_TEST_PUSH_CONSTANT_STAGES, + cvkLinks.GetModelViewPushConstantsSize(), + cvkLinks.GetHitTestPushConstants()); + + commandBuffer.BindGraphicsDescriptorSets(hPipelineLayout, pDescriptorSets.get(imageIndex)); + + commandBuffer.Draw(GetVertexCount()); + + ret = commandBuffer.FinishRecord(); + if (VkFailed(ret)) { return ret; } + + return ret; + } + + @Override + public int RecordHitTestCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int imageIndex){ + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssert(CVKDevice.GetCommandPoolHandle() != VK_NULL_HANDLE); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + // Set the hit test flag in the shaders to true + cvkLinks.UpdatePushConstantsHitTest(true); + + CVKCommandBuffer commandBuffer = hittestCommandBuffers.get(imageIndex); + CVKAssertNotNull(commandBuffer); + CVKAssertNotNull(hitTestPipelines.get(imageIndex)); + + ret = commandBuffer.BeginRecordSecondary(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, + inheritanceInfo); + if (VkFailed(ret)) { return ret; } + + commandBuffer.SetViewPort(cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight()); + commandBuffer.SetScissor(cvkVisualProcessor.GetCanvas().GetCurrentSurfaceExtent()); + + commandBuffer.BindGraphicsPipeline(hitTestPipelines.get(imageIndex)); + commandBuffer.BindVertexInput(cvkLinks.GetVertexBufferHandle(imageIndex)); + + // Push MV matrix to the vertex shader + commandBuffer.PushConstants(hPipelineLayout, + MODEL_VIEW_PUSH_CONSTANT_STAGES, + 0, + cvkLinks.GetModelViewPushConstants()); + + // Push drawHitTest flag to the geometry and fragment shader + commandBuffer.PushConstants(hPipelineLayout, + HIT_TEST_PUSH_CONSTANT_STAGES, + cvkLinks.GetModelViewPushConstantsSize(), + cvkLinks.GetHitTestPushConstants()); + + commandBuffer.BindGraphicsDescriptorSets(hPipelineLayout, pDescriptorSets.get(imageIndex)); + + commandBuffer.Draw(GetVertexCount()); + + ret = commandBuffer.FinishRecord(); + if (VkFailed(ret)) { return ret; } + + // Reset hit test flag to false + cvkLinks.UpdatePushConstantsHitTest(false); + + return ret; + } + + private void DestroyCommandBuffers() { + if (null != displayCommandBuffers) { + displayCommandBuffers.forEach(el -> {el.Destroy();}); + displayCommandBuffers.clear(); + displayCommandBuffers = null; + } + + if (null != hittestCommandBuffers) { + hittestCommandBuffers.forEach(el -> {el.Destroy();}); + hittestCommandBuffers.clear(); + hittestCommandBuffers = null; + } + } + + + // ========================> Descriptors <======================== \\ + + private int CreateDescriptorLayout() { + int ret; + + try (MemoryStack stack = stackPush()) { + VkDescriptorSetLayoutBinding.Buffer bindings = VkDescriptorSetLayoutBinding.callocStack(3, stack); + + // 0: Vertex uniform buffer + VkDescriptorSetLayoutBinding vertexUBDSLB = bindings.get(0); + vertexUBDSLB.binding(0); + vertexUBDSLB.descriptorCount(1); + vertexUBDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + vertexUBDSLB.pImmutableSamplers(null); + vertexUBDSLB.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + + // 1: Vertex samplerBuffer (position buffer) + VkDescriptorSetLayoutBinding vertexSamplerDSLB = bindings.get(1); + vertexSamplerDSLB.binding(1); + vertexSamplerDSLB.descriptorCount(1); + vertexSamplerDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + vertexSamplerDSLB.pImmutableSamplers(null); + vertexSamplerDSLB.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + + // 2: Geometry uniform buffer + VkDescriptorSetLayoutBinding geometryUBDSLB = bindings.get(2); + geometryUBDSLB.binding(2); + geometryUBDSLB.descriptorCount(1); + geometryUBDSLB.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + geometryUBDSLB.pImmutableSamplers(null); + geometryUBDSLB.stageFlags(VK_SHADER_STAGE_GEOMETRY_BIT); + + + VkDescriptorSetLayoutCreateInfo layoutInfo = VkDescriptorSetLayoutCreateInfo.callocStack(stack); + layoutInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO); + layoutInfo.pBindings(bindings); + + LongBuffer pDescriptorSetLayout = stack.mallocLong(1); + + ret = vkCreateDescriptorSetLayout(CVKDevice.GetVkDevice(), layoutInfo, null, pDescriptorSetLayout); + if (VkSucceeded(ret)) { + hDescriptorLayout = pDescriptorSetLayout.get(0); + GetLogger().info("CVKPerspectiveLinksRenderable created hDescriptorLayout: 0x%016X", hDescriptorLayout); + } + } + return ret; + } + + private void DestroyDescriptorLayout() { + GetLogger().info("CVKPerspectiveLinksRenderable destroying hDescriptorLayout: 0x%016X", hDescriptorLayout); + vkDestroyDescriptorSetLayout(CVKDevice.GetVkDevice(), hDescriptorLayout, null); + hDescriptorLayout = VK_NULL_HANDLE; + } + + private int CreateDescriptorSets(MemoryStack stack) { + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(cvkSwapChain); + + int ret; + + // The same layout is used for each descriptor set. Each image has a + // an identical copy of the descriptor set so allow the GPU and CPU to + // desynchronise (when there are 2 or more images in the swapchain). + final int imageCount = cvkSwapChain.GetImageCount(); + LongBuffer layouts = stack.mallocLong(imageCount); + for (int i = 0; i < imageCount; ++i) { + layouts.put(i, hDescriptorLayout); + } + + VkDescriptorSetAllocateInfo allocInfo = VkDescriptorSetAllocateInfo.callocStack(stack); + allocInfo.sType(VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO); + allocInfo.descriptorPool(cvkDescriptorPool.GetDescriptorPoolHandle()); + allocInfo.pSetLayouts(layouts); + + // Allocate the descriptor sets from the descriptor pool, they'll be unitialised + pDescriptorSets = MemoryUtil.memAllocLong(imageCount); + ret = vkAllocateDescriptorSets(CVKDevice.GetVkDevice(), allocInfo, pDescriptorSets); + if (VkFailed(ret)) { return ret; } + + for (int i = 0; i < pDescriptorSets.capacity(); ++i) { + GetLogger().info("CVKPerspectiveLinksRenderable allocated hDescriptorSet %d: 0x%016X", i, pDescriptorSets.get(i)); + } + + return UpdateDescriptorSets(stack); + } + + // TODO: do we gain anything by having buffered UBOs? + private int UpdateDescriptorSets(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(pDescriptorSets); + CVKAssert(pDescriptorSets.capacity() > 0); +// CVKAssertNotNull(vertexUniformBuffers); +// CVKAssert(vertexUniformBuffers.size() > 0); +// CVKAssertNotNull(geometryUniformBuffers); +// CVKAssert(geometryUniformBuffers.size() > 0); + + int ret = VK_SUCCESS; + + final int imageCount = cvkSwapChain.GetImageCount(); + + final long positionBufferSize = cvkVisualProcessor.GetPositionBufferSize(); + hPositionBuffer = cvkVisualProcessor.GetPositionBufferHandle(); + hPositionBufferView = cvkVisualProcessor.GetPositionBufferViewHandle(); + CVKAssertNotNull(hPositionBuffer); + CVKAssertNotNull(hPositionBufferView); + + // - Descriptor info structs - + // We create these to describe the different resources we want to address + // in shaders. We have one info struct per resource. We then create a + // write descriptor set structure for each resource for each image. For + // buffered resources like the the uniform buffers we wait to set the + // buffer resource until the image loop below. + + // Struct for the uniform buffer used by VertexIcon.vs + VkDescriptorBufferInfo.Buffer vertexUniformBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + // vertexUniformBufferInfo.buffer is set per imageIndex + vertexUniformBufferInfo.offset(0); + vertexUniformBufferInfo.range(CVKLinksRenderable.VertexUniformBufferObject.SizeOf()); + + // Struct for texel buffer (positions) used by VertexIcon.vs + VkDescriptorBufferInfo.Buffer positionsTexelBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + positionsTexelBufferInfo.buffer(hPositionBuffer); + positionsTexelBufferInfo.offset(0); + positionsTexelBufferInfo.range(positionBufferSize); + + // Struct for the uniform buffer used by VertexIcon.gs + VkDescriptorBufferInfo.Buffer geometryUniformBufferInfo = VkDescriptorBufferInfo.callocStack(1, stack); + // geometryBufferInfo.buffer is set per imageIndex + geometryUniformBufferInfo.offset(0); + geometryUniformBufferInfo.range(CVKLinksRenderable.GeometryUniformBufferObject.SizeOf()); + + // We need 3 write descriptors, 2 for uniform buffers and 1 for texel buffers + VkWriteDescriptorSet.Buffer descriptorWrites = VkWriteDescriptorSet.callocStack(3, stack); + + // Vertex uniform buffer + VkWriteDescriptorSet vertexUBDescriptorWrite = descriptorWrites.get(0); + vertexUBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + vertexUBDescriptorWrite.dstBinding(0); + vertexUBDescriptorWrite.dstArrayElement(0); + vertexUBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + vertexUBDescriptorWrite.descriptorCount(1); + vertexUBDescriptorWrite.pBufferInfo(vertexUniformBufferInfo); + + // Vertex texel buffer (positions) + VkWriteDescriptorSet positionsTBDescriptorWrite = descriptorWrites.get(1); + positionsTBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + positionsTBDescriptorWrite.dstBinding(1); + positionsTBDescriptorWrite.dstArrayElement(0); + positionsTBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); + positionsTBDescriptorWrite.descriptorCount(1); + positionsTBDescriptorWrite.pBufferInfo(positionsTexelBufferInfo); + positionsTBDescriptorWrite.pTexelBufferView(stack.longs(hPositionBufferView)); + + // Geometry uniform buffer + VkWriteDescriptorSet geometryUBDescriptorWrite = descriptorWrites.get(2); + geometryUBDescriptorWrite.sType(VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET); + geometryUBDescriptorWrite.dstBinding(2); + geometryUBDescriptorWrite.dstArrayElement(0); + geometryUBDescriptorWrite.descriptorType(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); + geometryUBDescriptorWrite.descriptorCount(1); + geometryUBDescriptorWrite.pBufferInfo(geometryUniformBufferInfo); + + for (int i = 0; i < imageCount; ++i) { + // Update the buffered resource buffers + vertexUniformBufferInfo.buffer(cvkLinks.GetVertexUniformBufferHandle(i)); + geometryUniformBufferInfo.buffer(cvkLinks.GetGeometryUniformBufferHandle(i)); + + // Set the descriptor set we're updating in each write struct + long descriptorSet = pDescriptorSets.get(i); + descriptorWrites.forEach(el -> {el.dstSet(descriptorSet);}); + + // Update the descriptors with a write and no copy + GetLogger().info("CVKPerspectiveLinksRenderable updating descriptorSet: 0x%016X", descriptorSet); + vkUpdateDescriptorSets(CVKDevice.GetVkDevice(), descriptorWrites, null); + } + + SetDescriptorSetsState(CVK_RESOURCE_CLEAN); + + return ret; + } + + private int DestroyDescriptorSets() { + int ret = VK_SUCCESS; + + if (pDescriptorSets != null) { + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(cvkDescriptorPool.GetDescriptorPoolHandle()); + GetLogger().fine("CVKPerspectiveLinksRenderable returning %d descriptor sets to the pool", pDescriptorSets.capacity()); + + for (int i = 0; i < pDescriptorSets.capacity(); ++i) { + GetLogger().info("CVKPerspectiveLinksRenderable freeing hDescriptorSet %d: 0x%016X", i, pDescriptorSets.get(i)); + } + + // After calling vkFreeDescriptorSets, all descriptor sets in pDescriptorSets are invalid. + ret = vkFreeDescriptorSets(CVKDevice.GetVkDevice(), cvkDescriptorPool.GetDescriptorPoolHandle(), pDescriptorSets); + pDescriptorSets = null; + checkVKret(ret); + } + + return ret; + } + + @Override + public int DestroyDescriptorPoolResources() { + int ret = VK_SUCCESS; + + if (cvkDescriptorPool != null) { + return DestroyDescriptorSets(); + } + + return ret; + } + + @Override + public void IncrementDescriptorTypeRequirements(CVKDescriptorPoolRequirements reqs, CVKDescriptorPoolRequirements perImageReqs) { + // Line.vs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]; + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER]; + + // LineLine.gs + ++perImageReqs.poolDescriptorTypeCounts[VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER]; + + // One set per image + ++perImageReqs.poolDesciptorSetCount; + } + + @Override + public int SetNewDescriptorPool(CVKDescriptorPool newDescriptorPool) { + int ret = super.SetNewDescriptorPool(newDescriptorPool); + if (VkFailed(ret)) { return ret; } + SetDescriptorSetsState(CVK_RESOURCE_NEEDS_REBUILD); + return ret; + } + + + // ========================> Pipelines <======================== \\ + + private int CreatePipelineLayout() { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(hDescriptorLayout); + + int ret; + try (MemoryStack stack = stackPush()) { + VkPushConstantRange.Buffer pushConstantRange; + pushConstantRange = VkPushConstantRange.calloc(2); + pushConstantRange.get(0).stageFlags(MODEL_VIEW_PUSH_CONSTANT_STAGES); + pushConstantRange.get(0).size(cvkLinks.GetModelViewPushConstantsSize()); + pushConstantRange.get(0).offset(0); + + pushConstantRange.get(1).stageFlags(HIT_TEST_PUSH_CONSTANT_STAGES); + pushConstantRange.get(1).size(cvkLinks.GetHitTestPushConstantsSize()); + pushConstantRange.get(1).offset(cvkLinks.GetModelViewPushConstantsSize()); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = VkPipelineLayoutCreateInfo.callocStack(stack); + pipelineLayoutInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO); + pipelineLayoutInfo.pSetLayouts(stack.longs(hDescriptorLayout)); + pipelineLayoutInfo.pPushConstantRanges(pushConstantRange); + LongBuffer pPipelineLayout = stack.longs(VK_NULL_HANDLE); + ret = vkCreatePipelineLayout(CVKDevice.GetVkDevice(), pipelineLayoutInfo, null, pPipelineLayout); + if (VkFailed(ret)) { return ret; } + hPipelineLayout = pPipelineLayout.get(0); + CVKAssert(hPipelineLayout != VK_NULL_HANDLE); + } + return ret; + } + + private void DestroyPipelineLayout() { + if (hPipelineLayout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(CVKDevice.GetVkDevice(), hPipelineLayout, null); + hPipelineLayout = VK_NULL_HANDLE; + } + } + + + // ========================> Display <======================== \\ + + @Override + public boolean NeedsDisplayUpdate() { + // Race condition: icons owns the position buffer and the update task (on + // the visual processor thread) may not yet be complete. If this happens + // before the first update is finished icons will have a vertexCount of + // 0 so it won't be able to create the position buffer yet. If we can't + // get a handle to the current position buffer then we just skip updating + // until we can. + if (cvkVisualProcessor.GetPositionBufferHandle() == VK_NULL_HANDLE) { + return false; + } + + if (hPositionBuffer != cvkVisualProcessor.GetPositionBufferHandle() || + hPositionBufferView != cvkVisualProcessor.GetPositionBufferViewHandle()) { + if (descriptorSetsState != CVK_RESOURCE_NEEDS_REBUILD) { + descriptorSetsState = CVK_RESOURCE_NEEDS_UPDATE; + } + } + +//!! Do we need to track the UBO and vertex states of links some how? +// Could change renderer to cache the results of NeedsDisplayUpdate and use that +// to decide which renderables to call DisplayUpdate on. This would allow +// CVKPerspectiveLinksRenderable to defer to CVKLinksRenderable. + return cvkLinks.GetVertexCount() > 0 && + (commandBuffersState != CVK_RESOURCE_CLEAN || + descriptorSetsState != CVK_RESOURCE_CLEAN || + pipelinesState != CVK_RESOURCE_CLEAN); + } + + @Override + public int DisplayUpdate() { + int ret = VK_SUCCESS; + cvkVisualProcessor.VerifyInRenderThread(); + + try (MemoryStack stack = stackPush()) { + // Descriptors (binding values to shaders parameters) + if (descriptorSetsState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateDescriptorSets(stack); + if (VkFailed(ret)) { return ret; } + } else if (descriptorSetsState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateDescriptorSets(stack); + if (VkFailed(ret)) { return ret; } + } + + // Command buffers (rendering commands enqueued on the GPU) + if (commandBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateCommandBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Pipelines (all the render state and resources in one object) + if (pipelinesState == CVK_RESOURCE_NEEDS_REBUILD) { + displayPipelines = new ArrayList<>(cvkSwapChain.GetImageCount()); + ret = CreatePipelines(cvkSwapChain.GetRenderPassHandle(), displayPipelines); + if (VkFailed(ret)) { return ret; } + + hitTestPipelines = new ArrayList<>(cvkSwapChain.GetImageCount()); + ret = CreatePipelines(cvkSwapChain.GetOffscreenRenderPassHandle(), hitTestPipelines); + if (VkFailed(ret)) { return ret; } + } + } + + return ret; + } + + + // ========================> Tasks <======================== \\ + // NOTE: we effectively have two levels of staging. The second level is pretty + // straightforward, we need the resources we render with to be in the most + // optimal memory possible: resident GPU memory. This memory cannot be written + // by the CPU so we can't update it directly, instead we update staging buffers + // that are both GPU and CPU writable then copy from that into the final GPU + // buffers. + // The first level of staging is required as our staging buffers are VkDevice + // resources and the device may not be initialised when these tasks are called. + // This is the case when a graph is loaded into a new tab, we need to be able + // to process the tasks that load the graph vertices etc before the device is + // ready to create staging buffers. For this reason we update local arrays + // in the BuildArray functions, then copy these to our staging buffers + // during the renderer's display loop (when the rendering lambda of each task + // is executed). This also means we don't need to synchronise these arrays + // created by the visual processor's thread with the staging buffers that have + // a lifespan entirely within the rendering thread (AWT event thread). This + // is possible because the arrays are locals in the task functions and aren't + // modified by the visual processor thread after they've been added to the + // queue of tasks for the renderer to process. + + + + + + // ========================> Helpers <======================== \\ +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKPointsRenderable.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKPointsRenderable.java new file mode 100644 index 0000000000..67354269fd --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKPointsRenderable.java @@ -0,0 +1,573 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.renderables; + +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.*; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.*; +import static org.lwjgl.vulkan.VK10.*; +import static org.lwjgl.system.MemoryUtil.*; +import static org.lwjgl.system.MemoryStack.stackPush; +import au.gov.asd.tac.constellation.utilities.graphics.Matrix44f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector3f; +import au.gov.asd.tac.constellation.utilities.visual.VisualAccess; +import au.gov.asd.tac.constellation.utilities.visual.VisualChange; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import au.gov.asd.tac.constellation.visual.vulkan.CVKRenderUpdateTask; +import au.gov.asd.tac.constellation.visual.vulkan.CVKSwapChain; +import au.gov.asd.tac.constellation.visual.vulkan.CVKVisualProcessor; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKBuffer; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKCommandBuffer; +import java.nio.ByteBuffer; +import java.nio.LongBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkCommandBufferInheritanceInfo; +import org.lwjgl.vulkan.VkPipelineLayoutCreateInfo; +import org.lwjgl.vulkan.VkPushConstantRange; +import org.lwjgl.vulkan.VkVertexInputAttributeDescription; +import org.lwjgl.vulkan.VkVertexInputBindingDescription; + + +public class CVKPointsRenderable extends CVKRenderable { + // The UBO staging buffers are a known size so created outside user events + private CVKBuffer cvkVertexUBStagingBuffer = null; + + // Resources recreated only through user events + private int vertexCount = 0; + private CVKBuffer cvkVertexStagingBuffer = null; + private CVKBuffer cvkVertexBuffer = null; + + // Swapchain dependent resources + private List displayCommandBuffers = null; + + // The vertex staging buffer are used by both the event + // thread and rendering thread so must be synchronised. + private ReentrantLock vertexStagingBufferLock = new ReentrantLock(); + + private ByteBuffer pushConstants = null; + + + // ========================> Classes <======================== \\ + + private static class Vertex { + private static final int BYTES = Vector3f.BYTES; + private final Vector3f vertex; + + public Vertex(final Vector3f vertex) { + this.vertex = vertex; + } + + private static void CopyTo(ByteBuffer buffer, Vertex[] vertices) { + for(Vertex vertex : vertices) { + buffer.putFloat(vertex.vertex.getX()); + buffer.putFloat(vertex.vertex.getY()); + buffer.putFloat(vertex.vertex.getZ()); + } + } + + private static VkVertexInputBindingDescription.Buffer GetBindingDescription() { + VkVertexInputBindingDescription.Buffer bindingDescription = VkVertexInputBindingDescription.callocStack(1); + bindingDescription.binding(0); + bindingDescription.stride(Vertex.BYTES); + bindingDescription.inputRate(VK_VERTEX_INPUT_RATE_VERTEX); + return bindingDescription; + } + + private static VkVertexInputAttributeDescription.Buffer GetAttributeDescriptions() { + VkVertexInputAttributeDescription.Buffer attributeDescriptions = VkVertexInputAttributeDescription.callocStack(1); + VkVertexInputAttributeDescription vertexDescription = attributeDescriptions.get(0); + vertexDescription.binding(0); + vertexDescription.location(0); + vertexDescription.format(VK_FORMAT_R32G32B32_SFLOAT); + vertexDescription.offset(0); + return attributeDescriptions.rewind(); + } + } + + @Override + protected VkVertexInputBindingDescription.Buffer GetVertexBindingDescription() { + return Vertex.GetBindingDescription(); + } + + @Override + protected VkVertexInputAttributeDescription.Buffer GetVertexAttributeDescriptions() { + return Vertex.GetAttributeDescriptions(); + } + + + // ========================> Shaders <======================== \\ + + @Override + protected String GetVertexShaderName() { return "PassThruPoint.vs"; } + + @Override + protected String GetFragmentShaderName() { return "PassThruPoint.fs"; } + + + // ========================> Lifetime <======================== \\ + + public CVKPointsRenderable(CVKVisualProcessor visualProcessor) { + super(visualProcessor); + } + + private void CreateUBOStagingBuffers() { + cvkVertexUBStagingBuffer = CVKBuffer.Create(Matrix44f.BYTES, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKPointRenderable.CreateUBOStagingBuffers cvkVertexUBStagingBuffer"); + } + + @Override + public int Initialise() { + int ret = super.Initialise(); + if (VkFailed(ret)) { return ret; } + + // Initialise push constants to identity mtx + CreatePushConstants(); + + ret = CreatePipelineLayout(); + if (VkFailed(ret)) { return ret; } + + CreateUBOStagingBuffers(); + + return ret; + } + + private void DestroyStagingBuffers() { + if (cvkVertexStagingBuffer != null) { + try { + vertexStagingBufferLock.lock(); + cvkVertexStagingBuffer.Destroy(); + cvkVertexStagingBuffer = null; + } finally { + vertexStagingBufferLock.unlock(); + } + } + if (cvkVertexUBStagingBuffer != null) { + cvkVertexUBStagingBuffer.Destroy(); + cvkVertexUBStagingBuffer = null; + } + } + + @Override + public void Destroy() { + DestroyCommandBuffers(); + DestroyVertexBuffer(); + DestroyPipelines(); + DestroyPipelineLayout(); + DestroyPushConstants(); + DestroyStagingBuffers(); + + CVKAssert(displayPipelines == null); + CVKAssert(hPipelineLayout == VK_NULL_HANDLE); + CVKAssert(cvkVertexBuffer == null); + CVKAssert(displayCommandBuffers == null); + CVKAssert(pushConstants == null); + } + + + // ========================> Swap chain <======================== \\ + + @Override + protected int DestroySwapChainResources() { + this.cvkSwapChain = null; + + // We only need to recreate these resources if the number of images in + // the swapchain changes or if this is the first call after the initial + // swapchain is created. + if (displayPipelines != null && swapChainImageCountChanged) { + DestroyVertexBuffer(); + DestroyCommandBuffers(); + DestroyPipelines(); + } + + return VK_SUCCESS; + } + + @Override + public int SetNewSwapChain(CVKSwapChain newSwapChain) { + int ret = super.SetNewSwapChain(newSwapChain); + if (VkFailed(ret)) { return ret; } + + if (swapChainImageCountChanged) { + // The number of images has changed, we need to rebuild all image + // buffered resources + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + SetCommandBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + SetDescriptorSetsState(CVK_RESOURCE_NEEDS_REBUILD); + SetPipelinesState(CVK_RESOURCE_NEEDS_REBUILD); + } + + return ret; + } + + + // ========================> Vertex buffers <======================== \\ + + private int CreateVertexBuffer() { + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + + // We can only create vertex buffers if we have something to put in them + if (vertexCount > 0) { + int vertexBufferSizeBytes = Vertex.BYTES * vertexCount; + cvkVertexBuffer = CVKBuffer.Create(vertexBufferSizeBytes, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + "CVKPointRenderable cvkVertexBuffer"); + + // Populate them with some values + return UpdateVertexBuffer(); + } + + return ret; + } + + private int UpdateVertexBuffer() { + cvkVisualProcessor.VerifyInRenderThread(); + CVKAssert(cvkVertexStagingBuffer != null); + CVKAssert(cvkVertexBuffer != null); + CVKAssert(cvkVertexStagingBuffer.GetBufferSize() == cvkVertexBuffer.GetBufferSize()); + int ret = VK_SUCCESS; + + try { + vertexStagingBufferLock.lock(); + + List DEBUG_vertexDescriptors = new ArrayList<>(); + DEBUG_vertexDescriptors.add(new CVKBuffer.DEBUG_CVKBufferElementDescriptor("x", Float.TYPE)); + DEBUG_vertexDescriptors.add(new CVKBuffer.DEBUG_CVKBufferElementDescriptor("y", Float.TYPE)); + DEBUG_vertexDescriptors.add(new CVKBuffer.DEBUG_CVKBufferElementDescriptor("z", Float.TYPE)); + cvkVertexStagingBuffer.DEBUGPRINT(DEBUG_vertexDescriptors); + + cvkVertexBuffer.CopyFrom(cvkVertexStagingBuffer); + } finally { + vertexStagingBufferLock.unlock(); + } + + // Note the staging buffer is not freed as we can simplify the update tasks + // by just updating it and then copying it over again during ProcessRenderTasks(). + SetVertexBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public int GetVertexCount() { return vertexCount; } + + private void DestroyVertexBuffer() { + if (cvkVertexBuffer != null) { + cvkVertexBuffer.Destroy(); + cvkVertexBuffer = null; + } + } + + // ========================> Push constants <======================== \\ + + private void CreatePushConstants() { + // Initialise push constants to identity mtx + pushConstants = memAlloc(Matrix44f.BYTES); + UpdatePushConstants(); + } + + private void UpdatePushConstants(){ + PutMatrix44f(pushConstants, cvkVisualProcessor.getDisplayModelViewProjectionMatrix()); + pushConstants.flip(); + } + + private void DestroyPushConstants() { + if (pushConstants != null) { + memFree(pushConstants); + pushConstants = null; + } + } + + + // ========================> Command buffers <======================== \\ + + public int CreateCommandBuffers(){ + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + int imageCount = cvkSwapChain.GetImageCount(); + + displayCommandBuffers = new ArrayList<>(imageCount); + + for (int i = 0; i < imageCount; ++i) { + CVKCommandBuffer buffer = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_SECONDARY, GetLogger(), String.format("CVKPointRenderable %d", i)); + displayCommandBuffers.add(buffer); + } + + SetCommandBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public VkCommandBuffer GetDisplayCommandBuffer(int imageIndex) { + return displayCommandBuffers.get(imageIndex).GetVKCommandBuffer(); + } + + @Override + public int RecordDisplayCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int imageIndex){ + CVKAssertNotNull(cvkSwapChain); + cvkVisualProcessor.VerifyInRenderThread(); + int ret; + + CVKCommandBuffer commandBuffer = displayCommandBuffers.get(imageIndex); + CVKAssert(commandBuffer != null); + CVKAssert(displayPipelines.get(imageIndex) != null); + + ret = commandBuffer.BeginRecordSecondary(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, + inheritanceInfo); + if (VkFailed(ret)) { return ret; } + + commandBuffer.SetViewPort(cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight()); + commandBuffer.SetScissor(cvkVisualProcessor.GetCanvas().GetCurrentSurfaceExtent()); + + commandBuffer.BindGraphicsPipeline(displayPipelines.get(imageIndex)); + commandBuffer.BindVertexInput(cvkVertexBuffer.GetBufferHandle()); + + commandBuffer.PushConstants(hPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, pushConstants); + commandBuffer.Draw(GetVertexCount()); + + ret = commandBuffer.FinishRecord(); + if (VkFailed(ret)) { return ret; } + + return ret; + } + + private void DestroyCommandBuffers() { + if (null != displayCommandBuffers) { + displayCommandBuffers.forEach(el -> {el.Destroy();}); + displayCommandBuffers.clear(); + displayCommandBuffers = null; + } + } + + + // ========================> Descriptors <======================== \\ + + @Override + public void IncrementDescriptorTypeRequirements(CVKDescriptorPool.CVKDescriptorPoolRequirements reqs, CVKDescriptorPool.CVKDescriptorPoolRequirements perImageReqs) { + // No descriptor sets required because axes use push constants instead of descriptor bound uniform buffers. + } + + @Override + public int DestroyDescriptorPoolResources() { + return VK_SUCCESS; + } + + + // ========================> Pipelines <======================== \\ + + private int CreatePipelineLayout() { + CVKAssert(CVKDevice.GetVkDevice() != null); + + int ret; + try (MemoryStack stack = stackPush()) { + VkPushConstantRange.Buffer pushConstantRange; + pushConstantRange = VkPushConstantRange.callocStack(1, stack); + pushConstantRange.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + pushConstantRange.size(Matrix44f.BYTES); + pushConstantRange.offset(0); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = VkPipelineLayoutCreateInfo.callocStack(stack); + pipelineLayoutInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO); + pipelineLayoutInfo.pPushConstantRanges(pushConstantRange); + LongBuffer pPipelineLayout = stack.longs(VK_NULL_HANDLE); + ret = vkCreatePipelineLayout(CVKDevice.GetVkDevice(), pipelineLayoutInfo, null, pPipelineLayout); + if (VkFailed(ret)) { return ret; } + hPipelineLayout = pPipelineLayout.get(0); + CVKAssert(hPipelineLayout != VK_NULL_HANDLE); + } + return ret; + } + + private void DestroyPipelineLayout() { + if (hPipelineLayout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(CVKDevice.GetVkDevice(), hPipelineLayout, null); + hPipelineLayout = VK_NULL_HANDLE; + } + } + + + // ========================> Display <======================== \\ + + @Override + public boolean NeedsDisplayUpdate() { + return vertexCount > 0 && + (vertexBuffersState != CVK_RESOURCE_CLEAN || + commandBuffersState != CVK_RESOURCE_CLEAN || + descriptorSetsState != CVK_RESOURCE_CLEAN || + pipelinesState != CVK_RESOURCE_CLEAN); + } + + @Override + public int DisplayUpdate() { + int ret = VK_SUCCESS; + cvkVisualProcessor.VerifyInRenderThread(); + + // Update vertex buffers + if (vertexBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + DestroyVertexBuffer(); + ret = CreateVertexBuffer(); + if (VkFailed(ret)) { return ret; } + } else if (vertexBuffersState == CVK_RESOURCE_NEEDS_UPDATE) { + ret = UpdateVertexBuffer(); + if (VkFailed(ret)) { return ret; } + } + + UpdatePushConstants(); + + // Command buffers (rendering commands enqueued on the GPU) + if (commandBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + DestroyCommandBuffers(); + ret = CreateCommandBuffers(); + if (VkFailed(ret)) { return ret; } + } + + // Pipelines (all the render state and resources in one object) + if (pipelinesState == CVK_RESOURCE_NEEDS_REBUILD) { + DestroyPipelines(); + displayPipelines = new ArrayList<>(cvkSwapChain.GetImageCount()); + ret = CreatePipelines(cvkSwapChain.GetRenderPassHandle(), displayPipelines); + if (VkFailed(ret)) { return ret; } + } + + return ret; + } + + + // ========================> Tasks <======================== \\ + + private void RebuildVertexStagingBuffer(final VisualAccess access, final int newVertexCount) { + // Note this will be called from the visual processer thread, not the render thread + try { + // Vertices are modified by the event thread + vertexStagingBufferLock.lock(); + + // Destroy old staging buffer if it exists + if (cvkVertexStagingBuffer != null) { + cvkVertexStagingBuffer.Destroy(); + cvkVertexStagingBuffer = null; + } + + if (newVertexCount > 0) { + int vertexBufferSizeBytes = Vertex.BYTES * newVertexCount; + cvkVertexStagingBuffer = CVKBuffer.Create(vertexBufferSizeBytes, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKPointRenderable.TaskCreateIcons cvkVertexStagingBuffer"); + + ByteBuffer pVertexMemory = cvkVertexStagingBuffer.StartMemoryMap(0, vertexBufferSizeBytes); + for (int pos = 0; pos < newVertexCount; pos++) { + final int offset = Vertex.BYTES * pos; + pVertexMemory.position(offset); + pVertexMemory.putFloat(access.getX(pos)); + pVertexMemory.putFloat(access.getY(pos)); + pVertexMemory.putFloat(access.getZ(pos)); + } + int vertMemPos = pVertexMemory.position(); + CVKAssert(vertMemPos == vertexBufferSizeBytes); + cvkVertexStagingBuffer.EndMemoryMap(); + pVertexMemory = null; // now unmapped, do not use + } + } finally { + vertexStagingBufferLock.unlock(); + } + } + + public CVKRenderUpdateTask TaskRebuildPoints(final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + final int newVertexCount = access.getVertexCount(); + GetLogger().info(String.format("TaskRebuildPoints frame %d: %d verts", cvkVisualProcessor.GetFrameNumber(), access.getVertexCount())); + RebuildVertexStagingBuffer(access, newVertexCount); + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + // We can't update the position buffer here as it is needed to render each image + // in the swap chain. If we recreate it for image 1 it will be likely be in + // flight for presenting image 0. The shared resource recreation path is + // synchronised for all images so we need to do it there. + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + + vertexCount = newVertexCount; + }; + } + + public CVKRenderUpdateTask TaskUpdatePoints(final VisualChange change, final VisualAccess access) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + // If this fires investigate why we didn't get a rebuild task first + final int newVertexCount = access.getVertexCount(); + GetLogger().info(String.format("TaskUpdatePoints frame %d: %d verts", cvkVisualProcessor.GetFrameNumber(), access.getVertexCount())); + CVKAssert(vertexCount == newVertexCount); //REMOVE AFTER TESTING + + // If we have had an update task called before a rebuild task we first have to build + // the staging buffer. Rebuild also if the vertex count has somehow changed. + final boolean rebuildRequired = cvkVertexStagingBuffer == null || + vertexCount != newVertexCount; + if (rebuildRequired) { + RebuildVertexStagingBuffer(access, newVertexCount); + } + + try { + vertexStagingBufferLock.lock(); + + // We map the whole range as GraphVisualAccess applies any per vertex change to all + // vertices in the accessGraph so the change will contain all vertices anyway. + ByteBuffer pVertexMemory = cvkVertexStagingBuffer.StartMemoryMap(0, (int)cvkVertexStagingBuffer.GetBufferSize()); + final int numChanges = change.getSize(); + for (int i = 0; i < numChanges; ++i) { + int pos = change.getElement(i); + final int offset = Vertex.BYTES * pos; + pVertexMemory.position(offset); + pVertexMemory.putFloat(access.getX(pos)); + pVertexMemory.putFloat(access.getY(pos)); + pVertexMemory.putFloat(access.getZ(pos)); + } + } finally { + vertexStagingBufferLock.unlock(); + } + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + if (rebuildRequired) { + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + + vertexCount = newVertexCount; + } else if (vertexBuffersState != CVK_RESOURCE_NEEDS_REBUILD) { + SetVertexBuffersState(CVK_RESOURCE_NEEDS_UPDATE); + } + }; + } + + public CVKRenderUpdateTask TaskUpdateCamera() { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + UpdatePushConstants(); + }; + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKRenderable.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKRenderable.java new file mode 100644 index 0000000000..6323ac3f0f --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKRenderable.java @@ -0,0 +1,555 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.renderables; + +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool.CVKDescriptorPoolRequirements; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import au.gov.asd.tac.constellation.visual.vulkan.CVKSwapChain; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkCommandBufferInheritanceInfo; +import au.gov.asd.tac.constellation.visual.vulkan.CVKVisualProcessor; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_CLEAN; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_NEEDS_REBUILD; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_NEEDS_UPDATE; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKGraphLogger; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKShaderUtils; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssert; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssertNotNull; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_DEBUGGING; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.GetParentMethodName; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkFailed; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.nio.LongBuffer; +import java.util.List; +import java.util.logging.Level; +import org.apache.commons.lang3.mutable.MutableLong; +import org.lwjgl.system.MemoryStack; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.system.MemoryUtil.memAllocInt; +import static org.lwjgl.vulkan.VK10.VK_BLEND_FACTOR_DST_ALPHA; +import static org.lwjgl.vulkan.VK10.VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; +import static org.lwjgl.vulkan.VK10.VK_BLEND_FACTOR_SRC_ALPHA; +import static org.lwjgl.vulkan.VK10.VK_BLEND_OP_ADD; +import static org.lwjgl.vulkan.VK10.VK_COLOR_COMPONENT_A_BIT; +import static org.lwjgl.vulkan.VK10.VK_COLOR_COMPONENT_B_BIT; +import static org.lwjgl.vulkan.VK10.VK_COLOR_COMPONENT_G_BIT; +import static org.lwjgl.vulkan.VK10.VK_COLOR_COMPONENT_R_BIT; +import static org.lwjgl.vulkan.VK10.VK_COMPARE_OP_LESS_OR_EQUAL; +import static org.lwjgl.vulkan.VK10.VK_CULL_MODE_NONE; +import static org.lwjgl.vulkan.VK10.VK_DYNAMIC_STATE_SCISSOR; +import static org.lwjgl.vulkan.VK10.VK_DYNAMIC_STATE_VIEWPORT; +import static org.lwjgl.vulkan.VK10.VK_FRONT_FACE_COUNTER_CLOCKWISE; +import static org.lwjgl.vulkan.VK10.VK_LOGIC_OP_COPY; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.VK_POLYGON_MODE_FILL; +import static org.lwjgl.vulkan.VK10.VK_PRIMITIVE_TOPOLOGY_POINT_LIST; +import static org.lwjgl.vulkan.VK10.VK_SAMPLE_COUNT_1_BIT; +import static org.lwjgl.vulkan.VK10.VK_SHADER_STAGE_FRAGMENT_BIT; +import static org.lwjgl.vulkan.VK10.VK_SHADER_STAGE_GEOMETRY_BIT; +import static org.lwjgl.vulkan.VK10.VK_SHADER_STAGE_VERTEX_BIT; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_SUCCESS; +import static org.lwjgl.vulkan.VK10.vkCreateGraphicsPipelines; +import static org.lwjgl.vulkan.VK10.vkDestroyPipeline; +import org.lwjgl.vulkan.VkGraphicsPipelineCreateInfo; +import org.lwjgl.vulkan.VkOffset2D; +import org.lwjgl.vulkan.VkPipelineColorBlendAttachmentState; +import org.lwjgl.vulkan.VkPipelineColorBlendStateCreateInfo; +import org.lwjgl.vulkan.VkPipelineDepthStencilStateCreateInfo; +import org.lwjgl.vulkan.VkPipelineDynamicStateCreateInfo; +import org.lwjgl.vulkan.VkPipelineInputAssemblyStateCreateInfo; +import org.lwjgl.vulkan.VkPipelineMultisampleStateCreateInfo; +import org.lwjgl.vulkan.VkPipelineRasterizationStateCreateInfo; +import org.lwjgl.vulkan.VkPipelineShaderStageCreateInfo; +import org.lwjgl.vulkan.VkPipelineVertexInputStateCreateInfo; +import org.lwjgl.vulkan.VkPipelineViewportStateCreateInfo; +import org.lwjgl.vulkan.VkRect2D; +import org.lwjgl.vulkan.VkVertexInputAttributeDescription; +import org.lwjgl.vulkan.VkVertexInputBindingDescription; +import org.lwjgl.vulkan.VkViewport; + +public abstract class CVKRenderable { + + protected enum CVKRenderableResourceState { + CVK_RESOURCE_CLEAN, + CVK_RESOURCE_NEEDS_UPDATE, + CVK_RESOURCE_NEEDS_REBUILD + } + + protected final CVKVisualProcessor cvkVisualProcessor; + protected CVKDescriptorPool cvkDescriptorPool = null; + protected CVKSwapChain cvkSwapChain = null; + protected boolean swapChainImageCountChanged = true; + protected boolean isInitialised = false; + protected long hDescriptorLayout = VK_NULL_HANDLE; + protected long hPipelineLayout = VK_NULL_HANDLE; + protected Long hVertexShaderModule = VK_NULL_HANDLE; + protected Long hGeometryShaderModule = VK_NULL_HANDLE; + protected Long hFragmentShaderModule = VK_NULL_HANDLE; + protected List displayPipelines = null; + + // Render states that can be overridden by each subclass + protected boolean colourBlend = true; + protected boolean depthTest = true; + protected boolean depthWrite = true; + protected boolean logicOpEnable = false; + protected int cullMode = VK_CULL_MODE_NONE; + protected int depthCompareOperation = VK_COMPARE_OP_LESS_OR_EQUAL; + protected int assemblyTopology = VK_PRIMITIVE_TOPOLOGY_POINT_LIST; + protected int srcColourBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + protected int dstColourBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + protected int colourBlendOp = VK_BLEND_OP_ADD; + protected int srcAlphaBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + protected int dstAlphaBlendFactor = VK_BLEND_FACTOR_DST_ALPHA; + protected int alphaBlendOp = VK_BLEND_OP_ADD; + protected int logicOp = VK_LOGIC_OP_COPY; + protected int colourWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + + // Resource states, not every type will be used by each renderable + protected CVKRenderableResourceState vertexUniformBufferState = CVK_RESOURCE_NEEDS_REBUILD; + protected CVKRenderableResourceState geometryUniformBufferState = CVK_RESOURCE_NEEDS_REBUILD; + protected CVKRenderableResourceState fragmentUniformBufferState = CVK_RESOURCE_NEEDS_REBUILD; + protected CVKRenderableResourceState vertexBuffersState = CVK_RESOURCE_NEEDS_REBUILD; + protected CVKRenderableResourceState commandBuffersState = CVK_RESOURCE_NEEDS_REBUILD; + protected CVKRenderableResourceState descriptorSetsState = CVK_RESOURCE_NEEDS_REBUILD; + protected CVKRenderableResourceState pipelinesState = CVK_RESOURCE_NEEDS_REBUILD; + + + // ========================> Debuggering <======================== \\ + + public boolean DEBUG_skipRender = false; + public boolean DebugSkipRender() { return DEBUG_skipRender; } + + protected static boolean LOGSTATECHANGE = false; + protected void SetVertexUniformBufferState(final CVKRenderableResourceState state) { + CVKAssert(!(vertexUniformBufferState == CVK_RESOURCE_NEEDS_REBUILD && state == CVK_RESOURCE_NEEDS_UPDATE)); + if (LOGSTATECHANGE) { + GetLogger().info("%d\t vertexUniformBufferState %s -> %s\tSource: %s", + cvkVisualProcessor.GetFrameNumber(), vertexUniformBufferState.name(), state.name(), GetParentMethodName()); + } + vertexUniformBufferState = state; + } + protected void SetGeometryUniformBufferState(final CVKRenderableResourceState state) { + CVKAssert(!(geometryUniformBufferState == CVK_RESOURCE_NEEDS_REBUILD && state == CVK_RESOURCE_NEEDS_UPDATE)); + if (LOGSTATECHANGE) { + GetLogger().info("%d\t geometryUniformBufferState %s -> %s\tSource: %s", + cvkVisualProcessor.GetFrameNumber(), geometryUniformBufferState.name(), state.name(), GetParentMethodName()); + } + geometryUniformBufferState = state; + } + protected void SetFragmentUniformBufferState(final CVKRenderableResourceState state) { + CVKAssert(!(fragmentUniformBufferState == CVK_RESOURCE_NEEDS_REBUILD && state == CVK_RESOURCE_NEEDS_UPDATE)); + if (LOGSTATECHANGE) { + GetLogger().info("%d\t fragmentUniformBufferState %s -> %s\tSource: %s", + cvkVisualProcessor.GetFrameNumber(), fragmentUniformBufferState.name(), state.name(), GetParentMethodName()); + } + fragmentUniformBufferState = state; + } + protected void SetVertexBuffersState(final CVKRenderableResourceState state) { + CVKAssert(!(vertexBuffersState == CVK_RESOURCE_NEEDS_REBUILD && state == CVK_RESOURCE_NEEDS_UPDATE)); + if (LOGSTATECHANGE) { + GetLogger().info("%d\t vertexBuffersState %s -> %s\tSource: %s", + cvkVisualProcessor.GetFrameNumber(), vertexBuffersState.name(), state.name(), GetParentMethodName()); + } + vertexBuffersState = state; + } + protected void SetCommandBuffersState(final CVKRenderableResourceState state) { + CVKAssert(!(commandBuffersState == CVK_RESOURCE_NEEDS_REBUILD && state == CVK_RESOURCE_NEEDS_UPDATE)); + if (LOGSTATECHANGE) { + GetLogger().info("%d\t commandBuffersState %s -> %s\tSource: %s", + cvkVisualProcessor.GetFrameNumber(), commandBuffersState.name(), state.name(), GetParentMethodName()); + } + commandBuffersState = state; + } + protected void SetDescriptorSetsState(final CVKRenderableResourceState state) { + CVKAssert(!(descriptorSetsState == CVK_RESOURCE_NEEDS_REBUILD && state == CVK_RESOURCE_NEEDS_UPDATE)); + if (LOGSTATECHANGE) { + GetLogger().info("%d\t descriptorSetsState %s -> %s\tSource: %s", + cvkVisualProcessor.GetFrameNumber(), descriptorSetsState.name(), state.name(), GetParentMethodName()); + } + descriptorSetsState = state; + } + protected void SetPipelinesState(final CVKRenderableResourceState state) { + CVKAssert(!(pipelinesState == CVK_RESOURCE_NEEDS_REBUILD && state == CVK_RESOURCE_NEEDS_UPDATE)); + if (LOGSTATECHANGE) { + GetLogger().info("%d\t pipelinesState %s -> %s\tSource: %s", + cvkVisualProcessor.GetFrameNumber(), pipelinesState.name(), state.name(), GetParentMethodName()); + } + pipelinesState = state; + } + + + // ========================> Shaders <======================== \\ + + protected String GetVertexShaderName() { return null; } + protected String GetGeometryShaderName() { return null; } + protected String GetFragmentShaderName() { return null; } + + + // ========================> Lifetime <======================== \\ + + public CVKRenderable(CVKVisualProcessor visualProcessor) { + cvkVisualProcessor = visualProcessor; + } + + + + /** + * This is called either when a new renderable is added to CVKRenderer or + * it the renderer has not been initialised itself at that point, called + * when the renderer is initialised. + * + * @return + */ + public int Initialise() { + int ret = VK_SUCCESS; + + MutableLong shaderModule = new MutableLong(VK_NULL_HANDLE); + final String vertexShaderName = GetVertexShaderName(); + if (vertexShaderName != null) { + ret = CVKShaderUtils.LoadShader(vertexShaderName, shaderModule); + if (VkFailed(ret)) { return ret; } + hVertexShaderModule = shaderModule.longValue(); + } + final String geometryShaderName = GetGeometryShaderName(); + if (geometryShaderName != null) { + ret = CVKShaderUtils.LoadShader(geometryShaderName, shaderModule); + if (VkFailed(ret)) { return ret; } + hGeometryShaderModule = shaderModule.longValue(); + } + final String fragmentShaderName = GetFragmentShaderName(); + if (fragmentShaderName != null) { + ret = CVKShaderUtils.LoadShader(fragmentShaderName, shaderModule); + if (VkFailed(ret)) { return ret; } + hFragmentShaderModule = shaderModule.longValue(); + } + + return ret; + } + + /** + * Cleanup, terminal, called when a graph is closing + */ + public abstract void Destroy(); + + protected CVKGraphLogger GetLogger() { return cvkVisualProcessor.GetLogger(); } + + /** + * Returns the command buffer for the current Image being sent + * to the GFX drivers + */ + public abstract VkCommandBuffer GetDisplayCommandBuffer(int imageIndex); + + /** + * Returns the hit test command buffer for the current Image being sent + * to the GFX drivers + */ + public VkCommandBuffer GetHitTestCommandBuffer(int imageIndex){ return null; } + + /** + * Called just before the swapchain is about to be destroyed allowing the + * object to cleanup its resources. + * + * @return error code + */ + protected abstract int DestroySwapChainResources(); + + /** + * Called just before the descriptor pool is about to be destroyed allowing the + * object to cleanup its descriptors. + * + * @return error code + */ + protected abstract int DestroyDescriptorPoolResources(); + + /** + * + * Called just after a new descriptor pool has been created but before the + * old one has been destroyed. This gives us a chance to cleanup resources + * created from the old pool and remember the new pool. Note we don't create + * the new descriptor pool resources until the next call to DisplayUpdate as + * at the point we are called the swapchain may also be pending recreation. + * + * @param newDescriptorPool + * @return error code + */ + public int SetNewDescriptorPool(CVKDescriptorPool newDescriptorPool) { + int ret = VK_SUCCESS; + + // If this isn't the initial update, release swapchain resources + if (cvkDescriptorPool != null) { + ret = DestroyDescriptorPoolResources(); + if (VkFailed(ret)) { return ret; } + } + + cvkDescriptorPool = newDescriptorPool; + + return ret; + } + + /** + * + * Called just after a new swapchain has been created but before the + * old one has been destroyed. This gives us a chance to cleanup resources + * created for the old swapchain and remember the new swapchain. Note we + * don't create the new swapchain resources until the next call to DisplayUpdate + * as at the point we are called the descriptor pool may also be pending recreation. + * + * @param newSwapChain + * @return error code + */ + public int SetNewSwapChain(CVKSwapChain newSwapChain) { + int ret = VK_SUCCESS; + + swapChainImageCountChanged = cvkSwapChain == null || + newSwapChain == null || + newSwapChain.GetImageCount() != cvkSwapChain.GetImageCount(); + + // If this isn't the initial update, release swapchain resources + if (cvkSwapChain != null) { + ret = DestroySwapChainResources(); + if (VkFailed(ret)) { return ret; } + } + + cvkSwapChain = newSwapChain; + + return ret; + } + + public abstract void IncrementDescriptorTypeRequirements(CVKDescriptorPoolRequirements reqs, CVKDescriptorPoolRequirements perImageReqs); + public abstract int RecordDisplayCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int index); + public int RecordHitTestCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int index) { return VK_SUCCESS; } + + /** + * @return Returns the number of vertices used in the vertex buffer + */ + public abstract int GetVertexCount(); + + + public boolean NeedsDisplayUpdate() { return false; } + public int DisplayUpdate() { return VK_SUCCESS; } + + + // ========================> Pipelines <======================== \\ + protected abstract VkVertexInputBindingDescription.Buffer GetVertexBindingDescription(); + protected abstract VkVertexInputAttributeDescription.Buffer GetVertexAttributeDescriptions(); + + protected int CreatePipelines(long renderPassHandle, List pipelines) { + CVKAssert(hPipelineLayout != VK_NULL_HANDLE); + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(cvkSwapChain); + CVKAssertNotNull(cvkDescriptorPool); + CVKAssertNotNull(cvkSwapChain.GetSwapChainHandle()); + CVKAssertNotNull(renderPassHandle != VK_NULL_HANDLE); + CVKAssertNotNull(cvkDescriptorPool.GetDescriptorPoolHandle()); + CVKAssertNotNull(hVertexShaderModule); + CVKAssertNotNull(hGeometryShaderModule); + CVKAssertNotNull(hFragmentShaderModule); + CVKAssert(cvkSwapChain.GetWidth() > 0); + CVKAssert(cvkSwapChain.GetHeight() > 0); + + int shaderStageCount = 0; + shaderStageCount += hVertexShaderModule != VK_NULL_HANDLE ? 1 : 0; + shaderStageCount += hGeometryShaderModule != VK_NULL_HANDLE ? 1 : 0; + shaderStageCount += hFragmentShaderModule != VK_NULL_HANDLE ? 1 : 0; + + final int imageCount = cvkSwapChain.GetImageCount(); + int ret = VK_SUCCESS; + try (MemoryStack stack = stackPush()) { + // A complete pipeline for each swapchain image. Wasteful? + for (int i = 0; i < imageCount; ++i) { + final ByteBuffer entryPoint = stack.UTF8("main"); + + int iShaderStage = 0; + VkPipelineShaderStageCreateInfo.Buffer shaderStages = VkPipelineShaderStageCreateInfo.callocStack(shaderStageCount, stack); + + if (hVertexShaderModule != VK_NULL_HANDLE) { + VkPipelineShaderStageCreateInfo vertShaderStageInfo = shaderStages.get(iShaderStage++); + vertShaderStageInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO); + vertShaderStageInfo.stage(VK_SHADER_STAGE_VERTEX_BIT); + vertShaderStageInfo.module(hVertexShaderModule); + vertShaderStageInfo.pName(entryPoint); + } + + if (hGeometryShaderModule != VK_NULL_HANDLE) { + VkPipelineShaderStageCreateInfo geomShaderStageInfo = shaderStages.get(iShaderStage++); + geomShaderStageInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO); + geomShaderStageInfo.stage(VK_SHADER_STAGE_GEOMETRY_BIT); + geomShaderStageInfo.module(hGeometryShaderModule); + geomShaderStageInfo.pName(entryPoint); + } + + if (hFragmentShaderModule != VK_NULL_HANDLE) { + VkPipelineShaderStageCreateInfo fragShaderStageInfo = shaderStages.get(iShaderStage++); + fragShaderStageInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO); + fragShaderStageInfo.stage(VK_SHADER_STAGE_FRAGMENT_BIT); + fragShaderStageInfo.module(hFragmentShaderModule); + fragShaderStageInfo.pName(entryPoint); + } + + // ===> VERTEX STAGE <=== + VkPipelineVertexInputStateCreateInfo vertexInputInfo = VkPipelineVertexInputStateCreateInfo.callocStack(stack); + vertexInputInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO); + vertexInputInfo.pVertexBindingDescriptions(GetVertexBindingDescription()); + vertexInputInfo.pVertexAttributeDescriptions(GetVertexAttributeDescriptions()); + + // ===> ASSEMBLY STAGE <=== + // Each point becomes two or more triangles in the geometry shader, but our input is a point list + VkPipelineInputAssemblyStateCreateInfo inputAssembly = VkPipelineInputAssemblyStateCreateInfo.callocStack(stack); + inputAssembly.sType(VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO); + inputAssembly.topology(assemblyTopology); + inputAssembly.primitiveRestartEnable(false); + + // ===> VIEWPORT & SCISSOR + VkViewport.Buffer viewport = VkViewport.callocStack(1, stack); + viewport.x(0.0f); + viewport.y(0.0f); + viewport.width(cvkSwapChain.GetWidth()); + viewport.height(cvkSwapChain.GetHeight()); + viewport.minDepth(0.0f); + viewport.maxDepth(1.0f); + + VkRect2D.Buffer scissor = VkRect2D.callocStack(1, stack); + scissor.offset(VkOffset2D.callocStack(stack).set(0, 0)); + scissor.extent(cvkSwapChain.GetExtent()); + + VkPipelineViewportStateCreateInfo viewportState = VkPipelineViewportStateCreateInfo.callocStack(stack); + viewportState.sType(VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO); + viewportState.pViewports(viewport); + viewportState.pScissors(scissor); + + // ===> RASTERIZATION STAGE <=== + VkPipelineRasterizationStateCreateInfo rasterizer = VkPipelineRasterizationStateCreateInfo.callocStack(stack); + rasterizer.sType(VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO); + rasterizer.depthClampEnable(false); + rasterizer.rasterizerDiscardEnable(false); + rasterizer.polygonMode(VK_POLYGON_MODE_FILL); + rasterizer.lineWidth(1.0f); + rasterizer.cullMode(cullMode); + rasterizer.frontFace(VK_FRONT_FACE_COUNTER_CLOCKWISE); + rasterizer.depthBiasEnable(false); + +// TODO: hook this up. See the block in CVKDevice where we query physical device caps. +// VkPipelineRasterizationLineStateCreateInfoEXT lineRasterInfo = VkPipelineRasterizationLineStateCreateInfoEXT.callocStack(stack); +// lineRasterInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_LINE_STATE_CREATE_INFO_EXT); +// lineRasterInfo.lineRasterizationMode(VK_LINE_RASTERIZATION_MODE_RECTANGULAR_SMOOTH_EXT); +// lineRasterInfo.stippledLineEnable(false); +// rasterizer.pNext(lineRasterInfo.address()); + + // ===> MULTISAMPLING <=== + VkPipelineMultisampleStateCreateInfo multisampling = VkPipelineMultisampleStateCreateInfo.callocStack(stack); + multisampling.sType(VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO); + multisampling.sampleShadingEnable(false); + multisampling.rasterizationSamples(VK_SAMPLE_COUNT_1_BIT); + + // ===> DEPTH <=== + VkPipelineDepthStencilStateCreateInfo depthStencil = VkPipelineDepthStencilStateCreateInfo.callocStack(stack); + depthStencil.sType(VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO); + depthStencil.depthTestEnable(depthTest); + depthStencil.depthWriteEnable(depthWrite); + depthStencil.depthCompareOp(depthCompareOperation); + depthStencil.depthBoundsTestEnable(false); + depthStencil.stencilTestEnable(false); + + // ===> COLOR BLENDING <=== + VkPipelineColorBlendAttachmentState.Buffer colorBlendAttachment = VkPipelineColorBlendAttachmentState.callocStack(1, stack); + colorBlendAttachment.colorWriteMask(colourWriteMask); + colorBlendAttachment.blendEnable(colourBlend); + if (colourBlend) { + colorBlendAttachment.srcColorBlendFactor(srcColourBlendFactor); + colorBlendAttachment.dstColorBlendFactor(dstColourBlendFactor); + colorBlendAttachment.colorBlendOp(colourBlendOp); + colorBlendAttachment.srcAlphaBlendFactor(srcAlphaBlendFactor); + colorBlendAttachment.dstAlphaBlendFactor(dstAlphaBlendFactor); + colorBlendAttachment.alphaBlendOp(alphaBlendOp); + } + + VkPipelineColorBlendStateCreateInfo colorBlending = VkPipelineColorBlendStateCreateInfo.callocStack(stack); + colorBlending.sType(VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO); + colorBlending.logicOpEnable(logicOpEnable); + colorBlending.logicOp(logicOp); + colorBlending.pAttachments(colorBlendAttachment); + colorBlending.blendConstants(stack.floats(0.0f, 0.0f, 0.0f, 0.0f)); + + // ===> DYNAMIC PROPERTIES CREATION <=== + IntBuffer pDynamicStates = memAllocInt(2); + pDynamicStates.put(VK_DYNAMIC_STATE_VIEWPORT); + pDynamicStates.put(VK_DYNAMIC_STATE_SCISSOR); + pDynamicStates.flip(); + VkPipelineDynamicStateCreateInfo dynamicState = VkPipelineDynamicStateCreateInfo.callocStack(stack); + dynamicState.sType(VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO); + dynamicState.pDynamicStates(pDynamicStates); + + // ===> PIPELINE CREATION <=== + VkGraphicsPipelineCreateInfo.Buffer pipelineInfo = VkGraphicsPipelineCreateInfo.callocStack(1, stack); + pipelineInfo.sType(VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO); + pipelineInfo.pStages(shaderStages); + pipelineInfo.pVertexInputState(vertexInputInfo); + pipelineInfo.pInputAssemblyState(inputAssembly); + pipelineInfo.pViewportState(viewportState); + pipelineInfo.pRasterizationState(rasterizer); + pipelineInfo.pMultisampleState(multisampling); + pipelineInfo.pDepthStencilState(depthStencil); + pipelineInfo.pColorBlendState(colorBlending); + pipelineInfo.layout(hPipelineLayout); + pipelineInfo.renderPass(renderPassHandle); + pipelineInfo.subpass(0); + pipelineInfo.basePipelineHandle(VK_NULL_HANDLE); + pipelineInfo.basePipelineIndex(-1); + pipelineInfo.pDynamicState(dynamicState); + + LongBuffer pGraphicsPipeline = stack.mallocLong(1); + ret = vkCreateGraphicsPipelines(CVKDevice.GetVkDevice(), + VK_NULL_HANDLE, + pipelineInfo, + null, + pGraphicsPipeline); + if (VkFailed(ret)) { return ret; } + CVKAssert(pGraphicsPipeline.get(0) != VK_NULL_HANDLE); + pipelines.add(pGraphicsPipeline.get(0)); + } + } + + SetPipelinesState(CVK_RESOURCE_CLEAN); + if (CVK_DEBUGGING) { + GetLogger().log(Level.INFO, "Graphics Pipeline created for class %s", getClass().getName()); + } + return ret; + } + + protected void DestroyPipelines() { + if (displayPipelines != null) { + for (int i = 0; i < displayPipelines.size(); ++i) { + vkDestroyPipeline(CVKDevice.GetVkDevice(), displayPipelines.get(i), null); + displayPipelines.set(i, VK_NULL_HANDLE); + } + displayPipelines.clear(); + displayPipelines = null; + SetPipelinesState(CVK_RESOURCE_NEEDS_REBUILD); + } + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKSelectionBoxRenderable.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKSelectionBoxRenderable.java new file mode 100644 index 0000000000..0147bdf539 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/renderables/CVKSelectionBoxRenderable.java @@ -0,0 +1,516 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.renderables; + +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.*; +import static org.lwjgl.system.MemoryUtil.*; +import static org.lwjgl.vulkan.VK10.*; +import au.gov.asd.tac.constellation.utilities.graphics.Matrix44f; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import java.nio.ByteBuffer; +import java.nio.LongBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkPipelineLayoutCreateInfo; +import org.lwjgl.vulkan.VkCommandBufferInheritanceInfo; +import org.lwjgl.vulkan.VkVertexInputAttributeDescription; +import org.lwjgl.vulkan.VkVertexInputBindingDescription; +import au.gov.asd.tac.constellation.utilities.graphics.Vector3f; +import au.gov.asd.tac.constellation.utilities.graphics.Vector4f; +import au.gov.asd.tac.constellation.utilities.visual.SelectionBoxModel; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDescriptorPool.CVKDescriptorPoolRequirements; +import au.gov.asd.tac.constellation.visual.vulkan.CVKVisualProcessor; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_CLEAN; +import static au.gov.asd.tac.constellation.visual.vulkan.renderables.CVKRenderable.CVKRenderableResourceState.CVK_RESOURCE_NEEDS_REBUILD; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKBuffer; +import au.gov.asd.tac.constellation.visual.vulkan.resourcetypes.CVKCommandBuffer; +import java.awt.Point; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.LinkedBlockingDeque; +import org.apache.commons.collections.CollectionUtils; +import org.lwjgl.PointerBuffer; +import static org.lwjgl.system.MemoryStack.stackPush; +import org.lwjgl.vulkan.VkPushConstantRange; + + +/******************************************************************************* + * CVKSelectionBoxRenderable + * + * This class renders a translucent box to show the nodes and transactions that + * will be select when the mouse button is released. + * + * This is the equivalent of au.gov.asd.tac.constellation.graph.interaction. + * visual.renderables.SelectionBoxRenderable in the JOGL display version. + *******************************************************************************/ + +public class CVKSelectionBoxRenderable extends CVKRenderable { + // From \CoreInteractiveGraph\src\au\gov\asd\tac\constellation\graph\interaction\visual\renderables\SelectionBoxRenderable.java + private static final int NUMBER_OF_VERTICES = 4; + private static final Vector4f SELECTION_COLOUR = new Vector4f(0, 0.5f, 1, 0.375f); + + // Copied from SelectionBoxRenderable this is the mechanism for synchronising input + // and rendering threads. Only the last 'model' is used. + private SelectionBoxModel selectionBoxModel = null; + private final BlockingDeque modelQueue = new LinkedBlockingDeque<>(); + + private final Vertex[] vertices = new Vertex[NUMBER_OF_VERTICES]; + private CVKBuffer cvkStagingBuffer = null; + private CVKBuffer cvkVertexBuffer = null; + private List displayCommandBuffers = null; + private ByteBuffer pushConstants = null; + + + // ========================> Classes <======================== \\ + + private static class Vertex { + + private static final int BYTES = Vector3f.BYTES + Vector4f.BYTES; + private static final int OFFSETOF_POS = 0; + private static final int OFFSETOF_COLOR = Vector3f.BYTES; + + private final Vector3f vertex; + private final Vector4f color; + + public Vertex(final Vector3f vertex, final Vector4f color) { + this.vertex = vertex; + this.color = color; + } + + public Vertex(final float x, final float y, final float z, final Vector4f color) { + this.vertex = new Vector3f(x, y, z); + this.color = color; + } + + private static void CopyTo(ByteBuffer buffer, Vertex[] vertices) { + for(Vertex vertex : vertices) { + buffer.putFloat(vertex.vertex.getX()); + buffer.putFloat(vertex.vertex.getY()); + buffer.putFloat(vertex.vertex.getZ()); + + buffer.putFloat(vertex.color.a[0]); + buffer.putFloat(vertex.color.a[1]); + buffer.putFloat(vertex.color.a[2]); + buffer.putFloat(vertex.color.a[3]); + } + } + + private static VkVertexInputBindingDescription.Buffer GetBindingDescription() { + + VkVertexInputBindingDescription.Buffer bindingDescription = + VkVertexInputBindingDescription.callocStack(1); + + bindingDescription.binding(0); + bindingDescription.stride(Vertex.BYTES); + bindingDescription.inputRate(VK_VERTEX_INPUT_RATE_VERTEX); + + return bindingDescription; + } + + private static VkVertexInputAttributeDescription.Buffer GetAttributeDescriptions() { + + VkVertexInputAttributeDescription.Buffer attributeDescriptions = + VkVertexInputAttributeDescription.callocStack(2); + + // Vertex + VkVertexInputAttributeDescription vertexDescription = attributeDescriptions.get(0); + vertexDescription.binding(0); + vertexDescription.location(0); + vertexDescription.format(VK_FORMAT_R32G32B32_SFLOAT); + vertexDescription.offset(OFFSETOF_POS); + + // Color + VkVertexInputAttributeDescription colorDescription = attributeDescriptions.get(1); + colorDescription.binding(0); + colorDescription.location(1); + colorDescription.format(VK_FORMAT_R32G32B32A32_SFLOAT); + colorDescription.offset(OFFSETOF_COLOR); + + return attributeDescriptions.rewind(); + } + } + + @Override + protected VkVertexInputBindingDescription.Buffer GetVertexBindingDescription() { + return Vertex.GetBindingDescription(); + } + + @Override + protected VkVertexInputAttributeDescription.Buffer GetVertexAttributeDescriptions() { + return Vertex.GetAttributeDescriptions(); + } + + + // ========================> Shaders <======================== \\ + + @Override + protected String GetVertexShaderName() { return "PassThru.vs"; } + + @Override + protected String GetGeometryShaderName() { return "PassThruTriangle.gs"; } + + @Override + protected String GetFragmentShaderName() { return "PassThru.fs"; } + + + // ========================> Lifetime <======================== \\ + + public CVKSelectionBoxRenderable(CVKVisualProcessor visualProcessor) { + super(visualProcessor); + colourBlend = true; + depthTest = false; + depthWrite = false; + assemblyTopology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; + } + + @Override + public int Initialise() { + int ret = super.Initialise(); + if (VkFailed(ret)) { return ret; } + + // Initialise push constants to identity mtx + CreatePushConstants(); + + ret = CreatePipelineLayout(); + if (VkFailed(ret)) { return ret; } + + return VK_SUCCESS; + } + + @Override + public void Destroy() { + DestroyCommandBuffers(); + DestroyVertexBuffer(); + DestroyPipelines(); + DestroyPipelineLayout(); + DestroyPushConstants(); + + CVKAssertNull(displayPipelines); + CVKAssertNull(hPipelineLayout); + CVKAssertNull(cvkVertexBuffer); + CVKAssertNull(displayCommandBuffers); + CVKAssertNull(pushConstants); + } + + + // ========================> Swap chain <======================== \\ + + @Override + protected int DestroySwapChainResources(){ + cvkVisualProcessor.VerifyInRenderThread(); + int ret = VK_SUCCESS; + + // We only need to recreate these resources if the number of images in + // the swapchain changes or if this is the first call after the initial + // swapchain is created. + if (displayPipelines != null && swapChainImageCountChanged) { + DestroyVertexBuffer(); + DestroyCommandBuffers(); + DestroyPipelines(); + + CVKAssertNull(displayPipelines); + CVKAssertNull(cvkVertexBuffer); + CVKAssertNull(displayCommandBuffers); + } + + return ret; + } + + + // ========================> Vertex buffers <======================== \\ + + private int CreateVertexBuffer(MemoryStack stack) { + CVKAssertNotNull(cvkSwapChain); + + // Allocate the vertex objects + vertices[0] = new Vertex(ZERO_3F, SELECTION_COLOUR); + vertices[1] = new Vertex(ZERO_3F, SELECTION_COLOUR); + vertices[2] = new Vertex(ZERO_3F, SELECTION_COLOUR); + vertices[3] = new Vertex(ZERO_3F, SELECTION_COLOUR); + + // Staging buffer so our VB can be device local (most performant memory) + final int size = vertices.length * Vertex.BYTES; + cvkStagingBuffer = CVKBuffer.Create(size, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKSelectionBoxRenderable.CreateVertexBuffer cvkStagingBuffer"); + + // Create the actual VB which will be device local + cvkVertexBuffer = CVKBuffer.Create(size, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + "CVKSelectionBoxRenderable.CreateVertexBuffers cvkStagingBuffer"); + cvkVertexBuffer.CopyFrom(cvkStagingBuffer); + + return UpdateVertexBuffer(stack); + } + + private int UpdateVertexBuffer(MemoryStack stack) { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssertNotNull(cvkStagingBuffer); + CVKAssertNotNull(cvkStagingBuffer.GetMemoryBufferHandle()); + + int ret = VK_SUCCESS; + + // Logic copied from SelectionBoxRenderable. + selectionBoxModel = null; + if (CollectionUtils.isNotEmpty(modelQueue)) { + selectionBoxModel = modelQueue.getLast(); + modelQueue.clear(); + } + + if (selectionBoxModel != null && selectionBoxModel.getEndPoint() != null) { + final Point begin = selectionBoxModel.getStartPoint(); + final Point end = selectionBoxModel.getEndPoint(); + + // Find the location of the box in projected coordinates + final float width = cvkSwapChain.GetWidth(); + final float height = cvkSwapChain.GetHeight(); + float left = (begin.x / width) * 2f - 1f; + float right = (end.x / width) * 2f - 1f; + float top = ((height - begin.y) / height) * 2f - 1f; + float bottom = ((height - end.y) / height) * 2f - 1f; + + vertices[0] = new Vertex(right, bottom, 0f, SELECTION_COLOUR); + vertices[1] = new Vertex(left, bottom, 0, SELECTION_COLOUR); + vertices[2] = new Vertex(left, top, 0f, SELECTION_COLOUR); + vertices[3] = new Vertex(right, top, 0f, SELECTION_COLOUR); + + // Update the staging buffer + final int size = vertices.length * Vertex.BYTES; + PointerBuffer data = stack.mallocPointer(1); + vkMapMemory(CVKDevice.GetVkDevice(), cvkStagingBuffer.GetMemoryBufferHandle(), 0, size, 0, data); + if (VkFailed(ret)) { return ret; } + { + Vertex.CopyTo(data.getByteBuffer(0, size), vertices); + } + vkUnmapMemory(CVKDevice.GetVkDevice(), cvkStagingBuffer.GetMemoryBufferHandle()); + + // Update the vertex buffer + cvkVertexBuffer.CopyFrom(cvkStagingBuffer); + } + + SetVertexBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public int GetVertexCount() { + if (selectionBoxModel != null && selectionBoxModel.isClear()) { + return NUMBER_OF_VERTICES; + } else { + return 0; + } + } + + private void DestroyVertexBuffer() { + if (null != cvkVertexBuffer) { + cvkVertexBuffer.Destroy(); + cvkVertexBuffer = null; + + SetVertexBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + } + } + + + // ========================> Push constants <======================== \\ + + private int CreatePushConstants() { + // Initialise push constants to identity mtx + pushConstants = memAlloc(Matrix44f.BYTES); + for (int iRow = 0; iRow < 4; ++iRow) { + for (int iCol = 0; iCol < 4; ++iCol) { + pushConstants.putFloat(IDENTITY_44F.get(iRow, iCol)); + } + } + pushConstants.flip(); + + return VK_SUCCESS; + } + + private void DestroyPushConstants() { + if (pushConstants != null) { + memFree(pushConstants); + pushConstants = null; + } + } + + + // ========================> Command buffers <======================== \\ + + public int CreateCommandBuffers() { + CVKAssertNotNull(cvkSwapChain); + + int ret = VK_SUCCESS; + int imageCount = cvkSwapChain.GetImageCount(); + + displayCommandBuffers = new ArrayList<>(imageCount); + + for (int i = 0; i < imageCount; ++i) { + CVKCommandBuffer buffer = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_SECONDARY, + GetLogger(), + String.format("CVKSelectionBoxRenderable %d", i)); + displayCommandBuffers.add(buffer); + } + + SetCommandBuffersState(CVK_RESOURCE_CLEAN); + + return ret; + } + + @Override + public VkCommandBuffer GetDisplayCommandBuffer(int imageIndex) { + return displayCommandBuffers.get(imageIndex).GetVKCommandBuffer(); + } + + @Override + public int RecordDisplayCommandBuffer(VkCommandBufferInheritanceInfo inheritanceInfo, int imageIndex) { + CVKAssertNotNull(cvkSwapChain); + cvkVisualProcessor.VerifyInRenderThread(); + int ret = VK_SUCCESS; + + CVKCommandBuffer commandBuffer = displayCommandBuffers.get(imageIndex); + CVKAssertNotNull(commandBuffer); + CVKAssertNotNull(displayPipelines.get(imageIndex)); + + ret = commandBuffer.BeginRecordSecondary(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, + inheritanceInfo); + if (VkFailed(ret)) { return ret; } + + commandBuffer.SetViewPort(cvkSwapChain.GetWidth(), cvkSwapChain.GetHeight()); + commandBuffer.SetScissor(cvkVisualProcessor.GetCanvas().GetCurrentSurfaceExtent()); + commandBuffer.BindGraphicsPipeline(displayPipelines.get(imageIndex)); + commandBuffer.BindVertexInput(cvkVertexBuffer.GetBufferHandle()); + commandBuffer.PushConstants(hPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, pushConstants); + + commandBuffer.Draw(GetVertexCount()); + + ret = commandBuffer.FinishRecord(); + if (VkFailed(ret)) { return ret; } + + return ret; + } + + private void DestroyCommandBuffers() { + if (null != displayCommandBuffers && displayCommandBuffers.size() > 0) { + displayCommandBuffers.forEach(el -> {el.Destroy();}); + displayCommandBuffers.clear(); + displayCommandBuffers = null; + + SetCommandBuffersState(CVK_RESOURCE_NEEDS_REBUILD); + } + } + + + // ========================> Descriptors <======================== \\ + + @Override + public void IncrementDescriptorTypeRequirements(CVKDescriptorPoolRequirements reqs, CVKDescriptorPoolRequirements perImageReqs) { + // No descriptor sets required because this class uses push constants instead of descriptor bound uniform buffers. + } + + @Override + public int DestroyDescriptorPoolResources() { + return VK_SUCCESS; + } + + + // ========================> Pipelines <======================== \\ + + private int CreatePipelineLayout() { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + + int ret; + try (MemoryStack stack = stackPush()) { + VkPushConstantRange.Buffer pushConstantRange; + pushConstantRange = VkPushConstantRange.callocStack(1, stack); + pushConstantRange.stageFlags(VK_SHADER_STAGE_VERTEX_BIT); + pushConstantRange.size(Matrix44f.BYTES); + pushConstantRange.offset(0); + + VkPipelineLayoutCreateInfo pipelineLayoutInfo = VkPipelineLayoutCreateInfo.callocStack(stack); + pipelineLayoutInfo.sType(VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO); + pipelineLayoutInfo.pPushConstantRanges(pushConstantRange); + LongBuffer pPipelineLayout = stack.longs(VK_NULL_HANDLE); + ret = vkCreatePipelineLayout(CVKDevice.GetVkDevice(), pipelineLayoutInfo, null, pPipelineLayout); + if (VkFailed(ret)) { return ret; } + hPipelineLayout = pPipelineLayout.get(0); + CVKAssertNotNull(hPipelineLayout); + } + return ret; + } + + private void DestroyPipelineLayout() { + if (hPipelineLayout != VK_NULL_HANDLE) { + vkDestroyPipelineLayout(CVKDevice.GetVkDevice(), hPipelineLayout, null); + hPipelineLayout = VK_NULL_HANDLE; + } + } + + + // ========================> Display <======================== \\ + + @Override + public boolean NeedsDisplayUpdate() { + return modelQueue.size() > 0; + } + + @Override + public int DisplayUpdate() { + cvkVisualProcessor.VerifyInRenderThread(); + + int ret = VK_SUCCESS; + + try (MemoryStack stack = stackPush()) { + + // We always update vertex buffers while we have a model in the queue + // as that updates the start and end points. CreateVertexBuffer + // calls UpdateVertexBuffer + if (vertexBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateVertexBuffer(stack); + if (VkFailed(ret)) { return ret; } + } else { + ret = UpdateVertexBuffer(stack); + if (VkFailed(ret)) { return ret; } + } + + if (commandBuffersState == CVK_RESOURCE_NEEDS_REBUILD) { + ret = CreateCommandBuffers(); + if (VkFailed(ret)) { return ret; } + } + + if (pipelinesState == CVK_RESOURCE_NEEDS_REBUILD) { + displayPipelines = new ArrayList<>(cvkSwapChain.GetImageCount()); + ret = CreatePipelines(cvkSwapChain.GetRenderPassHandle(), displayPipelines); + if (VkFailed(ret)) { return ret; } + } + } + + return ret; + } + + + // ========================> Tasks <======================== \\ + + public void queueModel(final SelectionBoxModel model) { + modelQueue.add(model); + cvkVisualProcessor.RequestRedraw(); + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKBuffer.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKBuffer.java new file mode 100644 index 0000000000..b31a2c73b1 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKBuffer.java @@ -0,0 +1,328 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.resourcetypes; + +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKGraphLogger; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssert; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_ALLOCATION_LOG_LEVEL; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkFailed; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.checkVKret; +import java.nio.ByteBuffer; +import java.nio.LongBuffer; +import java.util.List; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import static org.lwjgl.system.MemoryStack.stackPush; +import org.lwjgl.system.MemoryUtil; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_LEVEL_PRIMARY; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; +import static org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.VK_SHARING_MODE_EXCLUSIVE; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; +import static org.lwjgl.vulkan.VK10.vkAllocateMemory; +import static org.lwjgl.vulkan.VK10.vkBindBufferMemory; +import static org.lwjgl.vulkan.VK10.vkCmdCopyBuffer; +import static org.lwjgl.vulkan.VK10.vkCreateBuffer; +import static org.lwjgl.vulkan.VK10.vkDestroyBuffer; +import static org.lwjgl.vulkan.VK10.vkFreeMemory; +import static org.lwjgl.vulkan.VK10.vkGetBufferMemoryRequirements; +import static org.lwjgl.vulkan.VK10.vkMapMemory; +import static org.lwjgl.vulkan.VK10.vkUnmapMemory; +import org.lwjgl.vulkan.VkBufferCopy; +import org.lwjgl.vulkan.VkBufferCreateInfo; +import org.lwjgl.vulkan.VkMemoryAllocateInfo; +import org.lwjgl.vulkan.VkMemoryRequirements; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_DEBUGGING; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_VKALLOCATIONS; + +public class CVKBuffer { + final static int COPY_SIZE = 4096; //candidate for profiling + + private CVKGraphLogger cvkGraphLogger = null; + private LongBuffer pBuffer = MemoryUtil.memAllocLong(1); + protected LongBuffer pBufferMemory = MemoryUtil.memAllocLong(1); + protected long bufferSize = 0; + private PointerBuffer pWriteMemory = null; + private int properties = 0; + + private String DEBUGNAME = ""; + + // Use the static Create() method instead of direct construction + private CVKBuffer() { } + + public long GetBufferHandle() { return pBuffer.get(0); } + public long GetBufferSize() { return bufferSize; } + public long GetMemoryBufferHandle() { return pBufferMemory.get(0); } + private CVKGraphLogger GetLogger() { return cvkGraphLogger != null ? cvkGraphLogger : CVKGraphLogger.GetStaticLogger(); } + + public int CopyFrom(CVKBuffer other) { + CVKAssert(GetBufferSize() >= other.GetBufferSize()); + int ret; + + try (MemoryStack stack = stackPush()) { + CVKCommandBuffer cvkCopyCmd = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_PRIMARY, cvkGraphLogger, "CVKBuffer cvkCopyCmd"); + ret = cvkCopyCmd.Begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT); + if (VkFailed(ret)) { return ret; } + + VkBufferCopy.Buffer vkCopyRegions = VkBufferCopy.callocStack(1, stack); + VkBufferCopy vkBufferCopy = vkCopyRegions.get(0); + vkBufferCopy.dstOffset(0); + vkBufferCopy.srcOffset(0); + vkBufferCopy.size(other.GetBufferSize()); + + vkCmdCopyBuffer(cvkCopyCmd.GetVKCommandBuffer(), + other.GetBufferHandle(), + GetBufferHandle(), + vkCopyRegions); + ret = cvkCopyCmd.EndAndSubmit(); + cvkCopyCmd.Destroy(); + } + + return ret; + } + + /** + * + * @param pBytes, source ByteBuffer + * @param destOffset, where in our buffer to start the write + * @param srcOffset, where in pBytes to start the read + * @param size, how much to read/write + * @return VkResult code + */ + public int Put(ByteBuffer pBytes, int destOffset, int srcOffset, int size) { + int ret; + CVKAssert((properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0); + + // Check memory is not already mapped with an unfinished write + CVKAssert(pWriteMemory == null); + try (MemoryStack stack = stackPush()) { + PointerBuffer data = stack.mallocPointer(1); + + // Remember source position and limit so we can restore them post copy + int origPos = pBytes.position(); + int origLim = pBytes.limit(); + + // Map destOffset into our buffer into host writable memory + ret = vkMapMemory(CVKDevice.GetVkDevice(), GetMemoryBufferHandle(), destOffset, size, 0, data); //arg 5 is flags + if (VkFailed(ret)) { return ret; } + { + // Get a ByteBuffer representing the mapped memory, note offset is 0 as we offset in vkMapMemory + ByteBuffer dest = data.getByteBuffer(0, size); + + // Move to the source start position + pBytes.position(srcOffset); + + // Set the limit from there so we only copy size bytes even if both buffers would allow a bigger read/write + pBytes.limit(size + srcOffset); + + // Do the copy + dest.put(pBytes); + + // Reset pBytes to it's starting position and limit + pBytes.limit(origLim).position(origPos); + } + vkUnmapMemory(CVKDevice.GetVkDevice(), GetMemoryBufferHandle()); + } + + return ret; + } + + public ByteBuffer StartMemoryMap(int offset, int size) { + CVKAssert((properties & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0); + CVKAssert(pWriteMemory == null); + pWriteMemory = MemoryUtil.memAllocPointer(1); + vkMapMemory(CVKDevice.GetVkDevice(), GetMemoryBufferHandle(), offset, size, 0, pWriteMemory); + return pWriteMemory.getByteBuffer(size); + } + + public void EndMemoryMap() { + CVKAssert(pWriteMemory != null); + vkUnmapMemory(CVKDevice.GetVkDevice(), GetMemoryBufferHandle()); + MemoryUtil.memFree(pWriteMemory); + pWriteMemory = null; + } + + public static class DEBUG_CVKBufferElementDescriptor { + public final String label; + public final Class type; + public DEBUG_CVKBufferElementDescriptor(String inLabel, Class inType) { + label = inLabel; + type = inType; + } + } + public void DEBUGPRINT(List typeDescriptors) { + ByteBuffer pData = StartMemoryMap(0, (int)bufferSize); + + GetLogger().info("\n"); + GetLogger().info(String.format("Contents of %s:", DEBUGNAME)); + int idx = 0; + while (pData.hasRemaining()) { + for (DEBUG_CVKBufferElementDescriptor desc : typeDescriptors) { + if (desc.type == Float.TYPE) { + final float f = pData.getFloat(); + GetLogger().info(String.format("\tidx %d\t%s:\t%f", idx, desc.label, f)); + } else if (desc.type == Integer.TYPE) { + final int d = pData.getInt(); + GetLogger().info(String.format("\tidx %d\t%s:\t%d", idx, desc.label, d)); + } else if (desc.type == Byte.TYPE) { + // Assume we want to treat bytes as unsigned + final int b = pData.get() & 0xff; + GetLogger().info(String.format("\tidx %d\t%s:\t%d", idx, desc.label, b)); + } else { + GetLogger().info(String.format("CVKBuffer.DEBUGPRINT cannot handle type <%s>", desc.type.getName())); + break; + } + } + ++idx; + } + + GetLogger().info("\n"); + + EndMemoryMap(); + } + + /** + * java.nio.ByteBuffer and their like are unpooled heap buffers unlike DirectByteBuffer + * and HeapByteBuffer. The pooled buffers are zeroed by the JVM, the nio buffers on the + * other hand must be explicitly zeroed. + */ + public void ZeroMemory() { + try (MemoryStack stack = stackPush()) { + PointerBuffer data = stack.mallocPointer(1); + vkMapMemory(CVKDevice.GetVkDevice(), GetMemoryBufferHandle(), 0, bufferSize, 0, data); + { + ByteBuffer dest = data.getByteBuffer(0, (int)bufferSize); + MemoryUtil.memSet(dest, 0); + } + vkUnmapMemory(CVKDevice.GetVkDevice(), GetMemoryBufferHandle()); + } + } + + public void Destroy() { + if (CVK_DEBUGGING && pBuffer != null) { + final CVKGraphLogger logger = cvkGraphLogger != null ? cvkGraphLogger : CVKGraphLogger.GetStaticLogger(); + if (logger.isLoggable(CVK_ALLOCATION_LOG_LEVEL)) { + if (pBufferMemory != null && pBufferMemory.get(0) != VK_NULL_HANDLE) { + --CVK_VKALLOCATIONS; + logger.log(CVK_ALLOCATION_LOG_LEVEL, "CVK_VKALLOCATIONS (%d-) Destroy called on CVKBuffer %s (Buffer:0x%016X Memory:0x%016X), vkFreeMemory will be called", + CVK_VKALLOCATIONS, DEBUGNAME, pBuffer.get(0), pBufferMemory.get(0)); + } else { + logger.log(CVK_ALLOCATION_LOG_LEVEL, "CVK_VKALLOCATIONS (%d!) Destroy called on CVKBuffer %s (Buffer:0x%016X Memory:0x%016X), vkFreeMemory will NOT be called", + CVK_VKALLOCATIONS, CVK_VKALLOCATIONS, DEBUGNAME, pBuffer.get(0), pBufferMemory.get(0)); + } + } + } + if (pBuffer != null && pBuffer.get(0) != VK_NULL_HANDLE) { + vkDestroyBuffer(CVKDevice.GetVkDevice(), pBuffer.get(0), null); + pBuffer.put(0, VK_NULL_HANDLE); + MemoryUtil.memFree(pBuffer); + pBuffer = null; + } + if (pBufferMemory != null && pBufferMemory.get(0) != VK_NULL_HANDLE) { + vkFreeMemory(CVKDevice.GetVkDevice(), pBufferMemory.get(0), null); + pBufferMemory.put(0, VK_NULL_HANDLE); + MemoryUtil.memFree(pBufferMemory); + pBufferMemory = null; + } + } + + @SuppressWarnings("deprecation") + @Override + public void finalize() throws Throwable { + Destroy(); + super.finalize(); + } + + + /** + * Factory creation method for CVKBuffers + * + * @param size + * @param usage + * @param logger + * @param graphLogger + * @param properties + * @param debugName + * @return + */ + public static CVKBuffer Create( long size, + int usage, + int properties, + CVKGraphLogger graphLogger, + String debugName) { + CVKAssert(CVKDevice.GetVkDevice() != null); + + int ret; + CVKBuffer cvkBuffer = new CVKBuffer(); + cvkBuffer.cvkGraphLogger = graphLogger; + try(MemoryStack stack = stackPush()) { + cvkBuffer.bufferSize = size; + cvkBuffer.properties = properties; + + // Creating a buffer doesn't actually back it with memory. Thanks Vulkan. + VkBufferCreateInfo vkBufferInfo = VkBufferCreateInfo.callocStack(stack); + vkBufferInfo.sType(VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO); + vkBufferInfo.size(size); + vkBufferInfo.usage(usage); + + // This refers to sharing across device queues and seeing as we only have one queue... + vkBufferInfo.sharingMode(VK_SHARING_MODE_EXCLUSIVE); + + // Create the buffer, it isn't memory backed yet so not terribly useful + ret = vkCreateBuffer(CVKDevice.GetVkDevice(), + vkBufferInfo, + null, //alloc callbacks + cvkBuffer.pBuffer); + checkVKret(ret); + + // Calculate memory requirements based on the info we proved to the bufferInfo struct + VkMemoryRequirements vkMemoryRequirements = VkMemoryRequirements.mallocStack(stack); + vkGetBufferMemoryRequirements(CVKDevice.GetVkDevice(), cvkBuffer.pBuffer.get(0), vkMemoryRequirements); + + // Allocation info struct, type index needs a little logic as types can be mapped differently between GPUs + VkMemoryAllocateInfo vkAllocationInfo = VkMemoryAllocateInfo.callocStack(stack); + vkAllocationInfo.sType(VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO); + vkAllocationInfo.allocationSize(vkMemoryRequirements.size()); + vkAllocationInfo.memoryTypeIndex(CVKDevice.GetMemoryType(vkMemoryRequirements.memoryTypeBits(), properties)); + + // Allocate the memory needed for the buffer (still needs to be bound) + ret = vkAllocateMemory(CVKDevice.GetVkDevice(), vkAllocationInfo, null, cvkBuffer.pBufferMemory); + checkVKret(ret); + + // We have a pen, we have a apple, we have a pineapple...err bind the buffer to its memory + ret = vkBindBufferMemory(CVKDevice.GetVkDevice(), + cvkBuffer.pBuffer.get(0), + cvkBuffer.pBufferMemory.get(0), + 0); //this memory exists only for this buffer, so no offset + checkVKret(ret); + + if (CVK_DEBUGGING) { + cvkBuffer.DEBUGNAME = debugName; + final CVKGraphLogger logger = cvkBuffer.GetLogger(); + if (logger.isLoggable(CVK_ALLOCATION_LOG_LEVEL)) { + ++CVK_VKALLOCATIONS; + logger.log(CVK_ALLOCATION_LOG_LEVEL, String.format("CVK_VKALLOCATIONS(%d+) vkAllocateMemory(%d) for CVKBuffer %s (Buffer:0x%016X Memory:0x%016X)", + CVK_VKALLOCATIONS, vkMemoryRequirements.size(), cvkBuffer.DEBUGNAME, cvkBuffer.pBuffer.get(0), cvkBuffer.pBufferMemory.get(0))); + } + } + + return cvkBuffer; + } + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKCommandBuffer.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKCommandBuffer.java new file mode 100644 index 0000000000..030974ae08 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKCommandBuffer.java @@ -0,0 +1,445 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.resourcetypes; + + +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; +import static org.lwjgl.vulkan.VK10.vkAllocateCommandBuffers; +import static org.lwjgl.vulkan.VK10.vkBeginCommandBuffer; +import static org.lwjgl.vulkan.VK10.vkEndCommandBuffer; +import static org.lwjgl.vulkan.VK10.vkFreeCommandBuffers; +import org.lwjgl.PointerBuffer; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkCommandBufferAllocateInfo; +import org.lwjgl.vulkan.VkCommandBufferBeginInfo; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.*; +import java.nio.ByteBuffer; +import java.nio.LongBuffer; +import org.lwjgl.system.MemoryStack; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.VK_PIPELINE_BIND_POINT_COMPUTE; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_SUBMIT_INFO; +import static org.lwjgl.vulkan.VK10.vkCmdBeginRenderPass; +import static org.lwjgl.vulkan.VK10.vkCmdBindDescriptorSets; +import static org.lwjgl.vulkan.VK10.vkCmdBindVertexBuffers; +import static org.lwjgl.vulkan.VK10.vkCmdEndRenderPass; +import static org.lwjgl.vulkan.VK10.vkCmdPushConstants; +import static org.lwjgl.vulkan.VK10.vkCmdSetScissor; +import static org.lwjgl.vulkan.VK10.vkCmdSetViewport; +import static org.lwjgl.vulkan.VK10.vkQueueSubmit; +import static org.lwjgl.vulkan.VK10.vkQueueWaitIdle; +import org.lwjgl.vulkan.VkClearValue; +import org.lwjgl.vulkan.VkCommandBufferInheritanceInfo; +import org.lwjgl.vulkan.VkExtent2D; +import org.lwjgl.vulkan.VkOffset2D; +import org.lwjgl.vulkan.VkRect2D; +import org.lwjgl.vulkan.VkRenderPassBeginInfo; +import org.lwjgl.vulkan.VkSubmitInfo; +import org.lwjgl.vulkan.VkViewport; +import au.gov.asd.tac.constellation.utilities.graphics.Vector3f; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKGraphLogger; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKMissingEnums; +import org.lwjgl.system.MemoryUtil; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT; +import static org.lwjgl.vulkan.EXTDebugUtils.vkCmdBeginDebugUtilsLabelEXT; +import static org.lwjgl.vulkan.EXTDebugUtils.vkCmdEndDebugUtilsLabelEXT; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT; +import static org.lwjgl.vulkan.VK10.VK_DEPENDENCY_BY_REGION_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_ASPECT_COLOR_BIT; +import static org.lwjgl.vulkan.VK10.VK_INDEX_TYPE_UINT32; +import static org.lwjgl.vulkan.VK10.VK_PIPELINE_BIND_POINT_GRAPHICS; +import static org.lwjgl.vulkan.VK10.VK_QUEUE_FAMILY_IGNORED; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_MEMORY_BARRIER; +import static org.lwjgl.vulkan.VK10.vkCmdBindIndexBuffer; +import static org.lwjgl.vulkan.VK10.vkCmdBindPipeline; +import static org.lwjgl.vulkan.VK10.vkCmdDraw; +import static org.lwjgl.vulkan.VK10.vkCmdDrawIndexed; +import static org.lwjgl.vulkan.VK10.vkCmdPipelineBarrier; +import static org.lwjgl.vulkan.VK10.vkResetCommandBuffer; +import org.lwjgl.vulkan.VkDebugUtilsLabelEXT; +import org.lwjgl.vulkan.VkImageMemoryBarrier; +import org.lwjgl.vulkan.VkMemoryBarrier; + + +public class CVKCommandBuffer { + private VkCommandBuffer vkCommandBuffer = null; + private CVKGraphLogger cvkGraphLogger = null; + private String DEBUGNAME; + private VkDebugUtilsLabelEXT vkDebugLabel = null; + private CVKGraphLogger GetLogger() { return cvkGraphLogger != null ? cvkGraphLogger : CVKGraphLogger.GetStaticLogger(); } + + private CVKCommandBuffer() {} + + + public VkCommandBuffer GetVKCommandBuffer(){ return vkCommandBuffer; } + + @SuppressWarnings("deprecation") + @Override + public void finalize() throws Throwable { + //TODO remove the if, only here for CVK_DEBUGGING, its checked in Destroy() + if (vkCommandBuffer != null) { + Destroy(); + } + super.finalize(); + } + + public void Destroy(){ + if (vkCommandBuffer != null) { + if (CVK_DEBUGGING) { + final CVKGraphLogger logger = GetLogger(); + if (logger.isLoggable(CVK_ALLOCATION_LOG_LEVEL)) { + --CVK_VKALLOCATIONS; + logger.log(CVK_ALLOCATION_LOG_LEVEL, "CVK_VKALLOCATIONS(%d-) vkFreeCommandBuffers for %s 0x%016X", + CVK_VKALLOCATIONS, DEBUGNAME, vkCommandBuffer.address()); + } + } + + vkFreeCommandBuffers(CVKDevice.GetVkDevice(), CVKDevice.GetCommandPoolHandle(), vkCommandBuffer); + vkCommandBuffer = null; + } + } + + public int Begin(int flags) { + int ret; + try (MemoryStack stack = stackPush()) { + VkCommandBufferBeginInfo vkBeginInfo = VkCommandBufferBeginInfo.callocStack(stack); + vkBeginInfo.sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO); + vkBeginInfo.pNext(0); + vkBeginInfo.flags(flags); + ret = vkBeginCommandBuffer(vkCommandBuffer, vkBeginInfo); + } + return ret; + } + + public int EndAndSubmit() { + int ret; + try (MemoryStack stack = stackPush()) { + ret = vkEndCommandBuffer(vkCommandBuffer); + if (VkFailed(ret)) { return ret; } + + VkSubmitInfo.Buffer submitInfo = VkSubmitInfo.callocStack(1, stack); + submitInfo.sType(VK_STRUCTURE_TYPE_SUBMIT_INFO); + submitInfo.pCommandBuffers(stack.pointers(vkCommandBuffer)); + + ret = vkQueueSubmit(CVKDevice.GetVkQueue(), submitInfo, VK_NULL_HANDLE); + if (VkFailed(ret)) { return ret; } + ret = vkQueueWaitIdle(CVKDevice.GetVkQueue()); + } + return ret; + } + + public int BeginRecordSecondary(int flags, VkCommandBufferInheritanceInfo inheritanceInfo) { + int ret; + try (MemoryStack stack = stackPush()) { + VkCommandBufferBeginInfo beginInfo = VkCommandBufferBeginInfo.callocStack(stack); + beginInfo.sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO); + beginInfo.pNext(0); + beginInfo.flags(flags); + beginInfo.pInheritanceInfo(inheritanceInfo); + + ret = vkBeginCommandBuffer(vkCommandBuffer, beginInfo); + if (VkFailed(ret)) { return ret; } + + if (CVK_DEBUGGING && vkDebugLabel != null) { + vkCmdBeginDebugUtilsLabelEXT(vkCommandBuffer, vkDebugLabel); + } + } + + return ret; + } + + public int BeginRecordSecondary(int flags, long framebuffer, long renderPass, int subpass) { + int ret; + try (MemoryStack stack = stackPush()) { + VkCommandBufferInheritanceInfo inheritanceInfo = VkCommandBufferInheritanceInfo.callocStack(stack); + inheritanceInfo.sType(VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO); + inheritanceInfo.pNext(0); + inheritanceInfo.framebuffer(framebuffer); + inheritanceInfo.renderPass(renderPass); + inheritanceInfo.subpass(subpass); + inheritanceInfo.occlusionQueryEnable(false); + inheritanceInfo.queryFlags(0); + inheritanceInfo.pipelineStatistics(0); + + ret = BeginRecordSecondary(flags, inheritanceInfo); + } + return ret; + } + + public int FinishRecord() { + int ret; + + if (CVK_DEBUGGING && vkDebugLabel != null) { + vkCmdEndDebugUtilsLabelEXT(vkCommandBuffer); + } + + ret = vkEndCommandBuffer(vkCommandBuffer); + if (VkFailed(ret)) { return ret; } + + return ret; + } + + public void BeginRenderPass(long renderPass, long frameBuffer, + int width, int height, int colorAttachmentCount, int depthAttachment, + int contentsFlag) { + + VkClearValue.Buffer clearValues = VkClearValue.calloc(colorAttachmentCount + depthAttachment); + + for (int i=0; i Lifetime <======================== \\ + + public static boolean IsInstantiated() { + return cvkGlyphTextureAtlas != null; + } + + public static CVKGlyphTextureAtlas GetInstance() { + if (cvkGlyphTextureAtlas == null) { + cvkGlyphTextureAtlas = new CVKGlyphTextureAtlas(); + } + return cvkGlyphTextureAtlas; + } + + private CVKGlyphTextureAtlas() { + if (glyphManager == null) { + // If the first graph loaded is loaded from a file then this atlas is created + // before the device is initialised. In that case we just default the size of + // the glyph manager texture (2048) + if (CVKDevice.GetVkDevice() != null) { + texture2DDimension = Math.max(CVKDevice.GetMax2DDimension() / 2, GlyphManagerBI.DEFAULT_TEXTURE_BUFFER_SIZE); + } else { + texture2DDimension = GlyphManagerBI.DEFAULT_TEXTURE_BUFFER_SIZE; + } + glyphManager = new GlyphManagerBI(LabelFontsPreferenceKeys.getFontInfo(), + texture2DDimension, + GlyphManagerBI.DEFAULT_BUFFER_TYPE); + BACKGROUND_GLYPH_INDEX = glyphManager.createBackgroundGlyph(0.5f); + } + } + + + private int CreateAtlas() { + CVKAssert(glyphCount == 0); + CVKAssertNull(cvkAtlasImage); + CVKAssertNull(hAtlasSampler); + CVKAssertNull(cvkCoordinateBuffer); + CVKAssertNull(cvkCoordinateStagingBuffer); + int ret = VK_SUCCESS; + + // TODO: optimise this by not rewriting existing pages in the atlas texture (pre-allocation policy?) + // and only appending to the coordinate buffer (again, pre-allocation?) + glyphCount = glyphManager.getGlyphCount(); + if (glyphCount > 0) { + // Atlas texture + final int width = glyphManager.getTextureWidth(); + final int height = glyphManager.getTextureHeight(); + final int pageCount = glyphManager.getGlyphPageCount(); + final int pageSize = width * height * Byte.BYTES; //source BufferedImage format will be TYPE_BYTE_GRAY + CVKAssert(width == texture2DDimension); + CVKAssert(height == texture2DDimension); + + // Create destination image + cvkAtlasImage = CVKImage.Create(texture2DDimension, + texture2DDimension, + pageCount, + VK_FORMAT_R8_SRGB, //GL_RED in the OpenGL renderer + VK_IMAGE_VIEW_TYPE_2D_ARRAY, //regardless how how many layers are in the image, the shaders that use that atlas use a sampler2DArray + VK_IMAGE_TILING_OPTIMAL, //we usually sample rectangles rather than long straight lines + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, + null, + null, + "CVKGlyphTextureAtlas cvkAtlasImage"); + CVKAssertNotNull(cvkAtlasImage); + + // Transition image from undefined to transfer destination optimal + ret = cvkAtlasImage.Transition(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + if (VkFailed(ret)) { return ret; } + + CVKBuffer atlasStagingBuffer = CVKBuffer.Create(pageSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + null, + "CVKGlyphTextureAtlas.CreateAtlas atlasStagingBuffer"); + for (int page = 0; page < pageCount; ++ page) { + // Last page will likely be incomplete + if (page == (pageCount - 1)) { + atlasStagingBuffer.ZeroMemory(); + } + + // Copy from the glyph managers BufferedImage into our host visible staging buffer + ByteBuffer pMemory = atlasStagingBuffer.StartMemoryMap(0, pageSize); + { + glyphManager.readGlyphTexturePage(page, pMemory); + } + atlasStagingBuffer.EndMemoryMap(); + pMemory = null; + + // Copy it in. Note this is blocking. If this is highlighted as a performance issue use the + // add a version of CVKImage.CopyFrom that takes a command buffer but doesn't submit it and + // doesn't do any image layout transitions. + Vector3i dstOffset = new Vector3i(0, 0, page); + Vector3i dstExtent = new Vector3i(texture2DDimension, texture2DDimension, 1); + + ret = cvkAtlasImage.CopyFrom(atlasStagingBuffer, 0, dstOffset, dstExtent); + if (VkFailed(ret)) { return ret; } + } + + // Now the image is populated, transition it for reading + ret = cvkAtlasImage.Transition(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + if (VkFailed(ret)) { return ret; } + + // No longer needed + atlasStagingBuffer.Destroy(); + atlasStagingBuffer = null; + + // Create a sampler to match the image. Note the sampler allows us to sample + // an image but isn't tied to a specific image, note the lack of image or + // imageview parameters below. + try (MemoryStack stack = stackPush()) { + VkSamplerCreateInfo vkSamplerCreateInfo = VkSamplerCreateInfo.callocStack(stack); + vkSamplerCreateInfo.sType(VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO); + vkSamplerCreateInfo.maxAnisotropy(1.0f); + vkSamplerCreateInfo.magFilter(VK_FILTER_LINEAR); + vkSamplerCreateInfo.minFilter(VK_FILTER_LINEAR); + vkSamplerCreateInfo.mipmapMode(VK_SAMPLER_MIPMAP_MODE_LINEAR); + vkSamplerCreateInfo.addressModeU(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE); + vkSamplerCreateInfo.addressModeV(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE); + vkSamplerCreateInfo.addressModeW(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE); + vkSamplerCreateInfo.mipLodBias(0.0f); + vkSamplerCreateInfo.maxAnisotropy(8); + vkSamplerCreateInfo.compareOp(VK_COMPARE_OP_NEVER); + vkSamplerCreateInfo.minLod(0.0f); + vkSamplerCreateInfo.maxLod(0.0f); + vkSamplerCreateInfo.borderColor(VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE); + + LongBuffer pTextureSampler = stack.mallocLong(1); + ret = vkCreateSampler(CVKDevice.GetVkDevice(), vkSamplerCreateInfo, null, pTextureSampler); + checkVKret(ret); + hAtlasSampler = pTextureSampler.get(0); + CVKAssertNotNull(hAtlasSampler); + } + + // Coordinates staging buffer. The staging buffer and coordinate buffer + // must be separate buffers as they allocate memory from different pools, + // the first is host visible which we can copy other CPU buffers into, the + // second is device local which is not readable or writable from the CPU. + // This is where the staging buffer comes in as it can be written by the + // CPU but then read by the GPU and copied into the our device local + // buffer. + final int coordinateBufferSize = glyphCount * FLOATS_PER_GLYPH * Float.BYTES; + CVKBuffer coordinateStagingBuffer = CVKBuffer.Create(coordinateBufferSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + GetLogger(), + "CVKGlyphTextureAtlas.CreateAtlas coordinateStagingBuffer"); + ByteBuffer pMemory = coordinateStagingBuffer.StartMemoryMap(0, coordinateBufferSize); + { + float[] glyphCoordinates = glyphManager.getGlyphTextureCoordinates(); + for (int i = 0; i < glyphCount * FLOATS_PER_GLYPH; ++i) { + pMemory.putFloat(glyphCoordinates[i]); + } + } + coordinateStagingBuffer.EndMemoryMap(); + pMemory = null; // now unmapped, do not use + + // Coordinates buffer + cvkCoordinateBuffer = CVKBuffer.Create(coordinateStagingBuffer.GetBufferSize(), + VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + GetLogger(), + "CVKGlyphTextureAtlas.cvkCoordinateBuffer"); + ret = cvkCoordinateBuffer.CopyFrom(coordinateStagingBuffer); + if (VkFailed(ret)) { return ret; } + coordinateStagingBuffer.Destroy(); + coordinateStagingBuffer = null; + + // View (for the geometry shader sampler) + try (MemoryStack stack = stackPush()) { + // NB: we have already checked VK_FORMAT_R32G32B32A32_SFLOAT can be used as a texel buffer + // format in CVKDevice. If the format is changed here we need to check for its support in + // CVKDevice. + VkBufferViewCreateInfo vkViewInfo = VkBufferViewCreateInfo.callocStack(stack); + vkViewInfo.sType(VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO); + vkViewInfo.buffer(cvkCoordinateBuffer.GetBufferHandle()); + vkViewInfo.format(VK_FORMAT_R32G32B32A32_SFLOAT); + vkViewInfo.offset(0); + vkViewInfo.range(VK_WHOLE_SIZE); + + LongBuffer pBufferView = stack.mallocLong(1); + ret = vkCreateBufferView(CVKDevice.GetVkDevice(), vkViewInfo, null, pBufferView); + if (VkFailed(ret)) { return ret; } + hCoordinateBufferView = pBufferView.get(0); + GetLogger().info("Created CVKGlyphTextureAtlas.hCoordinateBufferView: 0x%016X", hCoordinateBufferView); + } + } + + return ret; + } + + private void Destroy() { + if (cvkAtlasImage != null) { + cvkAtlasImage.Destroy(); + cvkAtlasImage = null; + } + + if (hAtlasSampler != VK_NULL_HANDLE) { + vkDestroySampler(CVKDevice.GetVkDevice(), hAtlasSampler, null); + hAtlasSampler = VK_NULL_HANDLE; + } + + if (cvkCoordinateBuffer != null) { + cvkCoordinateBuffer.Destroy(); + cvkCoordinateBuffer = null; + } + + if (cvkCoordinateStagingBuffer != null) { + cvkCoordinateStagingBuffer.Destroy(); + cvkCoordinateStagingBuffer = null; + } + + glyphCount = 0; + } + + + // ========================> Display <======================== \\ + + public int DisplayUpdate() { + int ret = VK_SUCCESS; + + if (needsSaveToFile) { + ret = SaveToFile(fileToSave); + needsSaveToFile = false; + } + + if (glyphManager.getGlyphCount() > glyphCount) { + Destroy(); + ret = CreateAtlas(); + if (VkFailed(ret)) { return ret; } + } + + return ret; + } + + + // ========================> Tasks <======================== \\ + + public CVKRenderUpdateTask TaskSaveToFile(File file) { + //=== EXECUTED BY CALLING THREAD (VisualProcessor) ===// + + //=== EXECUTED BY RENDER THREAD (during CVKVisualProcessor.ProcessRenderTasks) ===// + return () -> { + needsSaveToFile = true; + fileToSave = file; + }; + } + + + // ========================> Helpers <======================== \\ + + public void RenderTextAsLigatures(final String text, GlyphManager.GlyphStream glyphStream, GlyphStreamContext context) { + glyphManager.renderTextAsLigatures(text, glyphStream, context); + } + + public float GetWidthScalingFactor() { return glyphManager.getWidthScalingFactor(); } + + public float GetHeightScalingFactor() { return glyphManager.getHeightScalingFactor(); } + + public int GetAtlasElementCount() { return glyphCount; } + + public long GetAtlasImageViewHandle() { return cvkAtlasImage.GetImageViewHandle(); } + + public long GetAtlasSamplerHandle() { return hAtlasSampler; } + + public long GetCoordinateBufferViewHandle() { return hCoordinateBufferView; } + + public long GetCoordinateBufferHandle() { return cvkCoordinateBuffer.GetBufferHandle(); } + + public long GetCoordinateBufferSize() { return cvkCoordinateBuffer.GetBufferSize(); } + + public int SaveToFile(File file) { return cvkAtlasImage.SaveToFile(file); } + + private CVKGraphLogger GetLogger() { return CVKGraphLogger.GetStaticLogger(); } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKIconAtlasSerializable.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKIconAtlasSerializable.java new file mode 100644 index 0000000000..cbb3043d6f --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKIconAtlasSerializable.java @@ -0,0 +1,88 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.resourcetypes; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import javax.imageio.ImageIO; + + +/** + * This class stores the minimal set of data needed to cache the icon atlas + * textures and dimensions to and from disk. This improves load times + * significantly. + */ +public class CVKIconAtlasSerializable implements Serializable { + public transient List layers = new ArrayList<>(); + public final LinkedHashMap loadedIcons = new LinkedHashMap<>(); + public int texture2DDimension = 0; + public int iconsPerLayer = 0; + public int iconsPerRowColumn = 0; + public int maxIcons = Short.MAX_VALUE; + + + public void Reset() { + texture2DDimension = 0; + iconsPerLayer = 0; + iconsPerRowColumn = 0; + maxIcons = Short.MAX_VALUE; + layers.clear(); + loadedIcons.clear(); + } + + + /** + * BufferedImages don't implement Serializable and therefore must be manually + * serialised. To do this we mark it transient so defaultWriteObject doesn't + * attempt to serialise it. We then use the ImageIO to serialize to or from + * the iostream. + * + * @param out: stream this object is serialized to + * @throws IOException + */ + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeInt(layers.size()); + for (BufferedImage eachImage : layers) { + ImageIO.write(eachImage, "png", out); // png is lossless + } + } + + /** + * BufferedImages don't implement Serializable and therefore must be manually + * serialised. To do this we mark it transient so defaultWriteObject doesn't + * attempt to serialise it. We then use the ImageIO to serialize to or from + * the iostream. + * + * @param in: stream this object is serialized from + * @throws IOException + */ + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + final int imageCount = in.readInt(); + layers = new ArrayList<>(imageCount); + for (int i = 0; i < imageCount; ++i) { + BufferedImage image = ImageIO.read(in); + layers.add(image); + } + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKIconTextureAtlas.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKIconTextureAtlas.java new file mode 100644 index 0000000000..4d85877ce3 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKIconTextureAtlas.java @@ -0,0 +1,783 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.resourcetypes; + +import au.gov.asd.tac.constellation.utilities.graphics.Vector3i; +import au.gov.asd.tac.constellation.utilities.icon.ConstellationIcon; +import au.gov.asd.tac.constellation.utilities.icon.DefaultIconProvider; +import au.gov.asd.tac.constellation.utilities.icon.IconManager; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import au.gov.asd.tac.constellation.visual.vulkan.CVKRenderUpdateTask; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKGraphLogger; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.checkVKret; +import java.awt.image.BufferedImage; +import static java.awt.image.BufferedImage.TYPE_4BYTE_ABGR; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssert; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssertNotNull; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssertNull; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_DEBUGGING; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkFailed; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.nio.ByteBuffer; +import java.nio.LongBuffer; +import java.util.ArrayList; +import java.util.List; +import org.lwjgl.system.MemoryStack; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.vulkan.VK10.VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; +import static org.lwjgl.vulkan.VK10.VK_BUFFER_USAGE_TRANSFER_SRC_BIT; +import static org.lwjgl.vulkan.VK10.VK_COMPARE_OP_NEVER; +import static org.lwjgl.vulkan.VK10.VK_FILTER_LINEAR; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_ASPECT_COLOR_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_TILING_OPTIMAL; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_USAGE_SAMPLED_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_USAGE_TRANSFER_DST_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_VIEW_TYPE_2D_ARRAY; +import static org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; +import static org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; +import static org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import static org.lwjgl.vulkan.VK10.VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; +import static org.lwjgl.vulkan.VK10.VK_SAMPLER_MIPMAP_MODE_LINEAR; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_SUCCESS; +import static org.lwjgl.vulkan.VK10.vkCreateSampler; +import static org.lwjgl.vulkan.VK10.vkDestroySampler; +import org.lwjgl.vulkan.VkSamplerCreateInfo; +import java.io.File; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import static org.lwjgl.system.MemoryUtil.memAlloc; +import static org.lwjgl.vulkan.VK10.VK_COMPONENT_SWIZZLE_A; +import static org.lwjgl.vulkan.VK10.VK_COMPONENT_SWIZZLE_B; +import static org.lwjgl.vulkan.VK10.VK_COMPONENT_SWIZZLE_G; +import static org.lwjgl.vulkan.VK10.VK_COMPONENT_SWIZZLE_R; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_USAGE_TRANSFER_SRC_BIT; +import org.lwjgl.vulkan.VkComponentMapping; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_ERROR_ICON_ATLAS_COPY_TIMEDOUT; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_ERROR_ICON_ATLAS_SAMPLER_CREATION_FAILED; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_ERROR_ICON_ATLAS_UNSUPPORTED_ICON_FORMAT; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_ICON_ATLAS_CACHING; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import static org.lwjgl.system.MemoryUtil.memFree; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8A8_UNORM; +import org.openide.modules.Places; + + +public final class CVKIconTextureAtlas { + private static CVKIconTextureAtlas cvkIconTextureAtlas = null; + private static boolean RUNTIME_SWIZZLE = true; + + public static final int ICON_DIMENSION = 256; + public static final int ICON_COMPONENTS = 4; //ARGB, 1 byte each + public static final int ICON_SIZE_PIXELS = ICON_DIMENSION * ICON_DIMENSION; + public static final int ICON_SIZE_BYTES = ICON_SIZE_PIXELS * ICON_COMPONENTS; + + // These icons must be permanently present at these pre-defined indexes. + // The shaders expect them to be there. + public static final int HIGHLIGHTED_ICON_INDEX = 0; + public static final String HIGHLIGHTED_ICON = DefaultIconProvider.HIGHLIGHTED.getExtendedName(); + public static final int UNKNOWN_ICON_INDEX = 1; + public static final String UNKNOWN_ICON = DefaultIconProvider.UNKNOWN.getExtendedName(); + + // Icons for drawing loops. + public static final int LOOP_DIRECTED_ICON_INDEX = 2; + public static final String LOOP_DIRECTED_ICON = DefaultIconProvider.LOOP_DIRECTED.getExtendedName(); + public static final int LOOP_UNDIRECTED_ICON_INDEX = 3; + public static final String LOOP_UNDIRECTED_ICON = DefaultIconProvider.LOOP_UNDIRECTED.getExtendedName(); + + // Noise indicator to be drawn when there are too many icons for the texture array. + public static final int NOISE_ICON_INDEX = 4; + public static final String NOISE_ICON = DefaultIconProvider.NOISE.getExtendedName(); + + // Transparency. + public static final int TRANSPARENT_ICON_INDEX = 5; + public static final String TRANSPARENT_ICON = DefaultIconProvider.TRANSPARENT.getExtendedName(); + + // Instance members + public CVKIconAtlasSerializable serializableData = new CVKIconAtlasSerializable(); + private CVKImage cvkAtlasImage = null; + private long hAtlasSampler = VK_NULL_HANDLE; + private int lastTransferedIconCount = 0; + private volatile boolean needsSaveToFile = false; + + private boolean cacheLoaded = false; + + + // ========================> Classes <======================== \\ + + // This could be replaced with a templated Pair type + private class IndexedConstellationIcon { + public final int index; + public final ConstellationIcon icon; + IndexedConstellationIcon(int index, ConstellationIcon icon) { + this.index = index; + this.icon = icon; + } + } + + + // ========================> Lifetime <======================== \\ + + public static boolean IsInstantiated() { + return cvkIconTextureAtlas != null; + } + + public static CVKIconTextureAtlas GetInstance() { + if (cvkIconTextureAtlas == null) { + cvkIconTextureAtlas = new CVKIconTextureAtlas(); + } + return cvkIconTextureAtlas; + } + + private CVKIconTextureAtlas() { + // Loads the user's cached atlas if it exists, this speeds up load times + // substantially as the dynamic atlas creation is the single slowest + // part of graph loads. + if (CVK_ICON_ATLAS_CACHING) { + LoadAtlasCache(); + } + + // These icons are guaranteed to be in the iconMap in this order. + // They must be at these pre-defined indices so other code (in particular the shaders) can use them. + // See *_INDEX constants above. + for (final String iconName : new String[]{HIGHLIGHTED_ICON, UNKNOWN_ICON, LOOP_DIRECTED_ICON, LOOP_UNDIRECTED_ICON, NOISE_ICON, TRANSPARENT_ICON}) { + AddIcon(iconName); + } + } + + private static int NextPowerOfTwo(int num) { + int highestOneBit = Integer.highestOneBit(num); + if (num == highestOneBit) { + return num; + } + return highestOneBit << 1; + } + + private static int CalculateMinimumTextureDimension(final int numberOfIcons, final int maxTextureDimension) { + final int maxIconsPerDimension = maxTextureDimension / ICON_DIMENSION; + final int iconsPerDimension = (int)(Math.ceil(Math.sqrt(numberOfIcons))); + if (iconsPerDimension < maxIconsPerDimension) { + return NextPowerOfTwo(iconsPerDimension * ICON_DIMENSION); + } else { + return maxIconsPerDimension * ICON_DIMENSION; + } + } + + /** + * Creates the atlas image, the required metadata and populates the image + * from the cached serialisableData or composites the image from many + * individual icons in the loadedIcons map. From cache is much faster. + * + * @param createFromSerializedData: use cache if true, loadedIcons otherwise. + * @return error code or VK_SUCCESS + */ + private int CreateAtlas(boolean createFromSerializedData) { + CVKAssert(lastTransferedIconCount == 0); + CVKAssertNull(cvkAtlasImage); + CVKAssertNull(hAtlasSampler); + + int ret = VK_SUCCESS; + + try (MemoryStack stack = stackPush()) { + if (!createFromSerializedData) { + serializableData.texture2DDimension = CalculateMinimumTextureDimension(serializableData.loadedIcons.size(), CVKDevice.GetMax2DDimension()); + serializableData.iconsPerRowColumn = serializableData.texture2DDimension / ICON_DIMENSION; + serializableData.iconsPerLayer = serializableData.iconsPerRowColumn * serializableData.iconsPerRowColumn; + serializableData.maxIcons = serializableData.iconsPerLayer * CVKDevice.GetMaxImageLayers(); + GetLogger().info("Icon atlas will be %dx%d to accomodate %d icons:\n\t%d icons per dimension\n\t%d icons per layer\n\t%d maximum icons", + serializableData.texture2DDimension, serializableData.texture2DDimension, serializableData.loadedIcons.size(), serializableData.iconsPerRowColumn, serializableData.iconsPerLayer, serializableData.maxIcons); + + if (serializableData.loadedIcons.size() > 0) { + List allIcons = new ArrayList<>(); + serializableData.loadedIcons.entrySet().forEach(entry -> { + allIcons.add(new IndexedConstellationIcon(entry.getValue(), IconManager.getIcon(entry.getKey()))); + }); + + ret = AddIconsToAtlas(allIcons); + checkVKret(ret); + lastTransferedIconCount = serializableData.loadedIcons.size(); + } + } else { + // Create destination image + cvkAtlasImage = CVKImage.Create(serializableData.texture2DDimension, + serializableData.texture2DDimension, + serializableData.layers.size(), + VK_FORMAT_R8G8B8A8_UNORM, //non-linear format to give more fidelity to the hues we are most able to perceive + VK_IMAGE_VIEW_TYPE_2D_ARRAY, //regardless how how many layers are in the image, the shaders that use that atlas use a sampler2DArray + VK_IMAGE_TILING_OPTIMAL, //we usually sample rectangles rather than long straight lines + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, + null, //the cached image was blitted to a PNG format which transformed the source pixes to RGBA, so no swizzling is necessary. + null, //no instanced logger as this class is a singleton + "CVKIconTextureAtlas cvkAtlasImage"); + + // Transition image from undefined to transfer destination optimal + ret = cvkAtlasImage.Transition(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + if (VkFailed(ret)) { return ret; } + + // Copy each layer in + final int layerSizeBytes = serializableData.texture2DDimension * serializableData.texture2DDimension * ICON_COMPONENTS; + for (int layer = 0; layer < serializableData.layers.size(); ++layer) { + // Copy it in. Note this is blocking. If this is highlighted as a performance issue use the + // add a version of CVKImage.CopyFrom that takes a command buffer but doesn't submit it and + // doesn't do any image layout transitions. + Vector3i dstOffset = new Vector3i(0, 0, layer); + Vector3i dstExtent = new Vector3i(serializableData.texture2DDimension, serializableData.texture2DDimension, 1); + + final BufferedImage image = serializableData.layers.get(layer); + GetLogger().warning("CopyBufferedImageToAtlas layer %d", layer); + ret = CopyBufferedImageToAtlas(image, + dstOffset, + dstExtent, + layerSizeBytes, + false); + if (VkFailed(ret)) { return ret; } + } + + // We no longer need a copy of the cached layers + serializableData.layers.clear(); + lastTransferedIconCount = serializableData.loadedIcons.size(); + + // Now the image is populated, transition it for reading + ret = cvkAtlasImage.Transition(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + if (VkFailed(ret)) { return ret; } + } + + + // Create sampler for fragment shaders + CreateAtlasSampler(stack); + } + + return ret; + } + + private void Destroy() { + if (cvkAtlasImage != null) { + cvkAtlasImage.Destroy(); + cvkAtlasImage = null; + } + + if (hAtlasSampler != VK_NULL_HANDLE) { + vkDestroySampler(CVKDevice.GetVkDevice(), hAtlasSampler, null); + hAtlasSampler = VK_NULL_HANDLE; + } + + lastTransferedIconCount = 0; + } + + + // ========================> Display <======================== \\ + + public int DisplayUpdate() { + int ret = VK_SUCCESS; + + while (needsSaveToFile) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + GetLogger().LogException(e, "Exception while waiting for Atlas to save to file"); + } + } + + // Visual processor thread read our serializableData from disk, now + // on the first update, use it to create the atlas + if (CVK_ICON_ATLAS_CACHING && !cacheLoaded && serializableData.layers.size() > 0) { + ret = CreateAtlas(true); + if (VkFailed(ret)) { return ret; } + cacheLoaded = true; + } else if (serializableData.loadedIcons.size() > lastTransferedIconCount) { + Destroy(); + ret = CreateAtlas(false); + if (VkFailed(ret)) { return ret; } + } + + return ret; + } + + + // ========================> Tasks <======================== \\ + + public CVKRenderUpdateTask TaskSaveToFile(File file) { + return () -> { + needsSaveToFile = true; + SaveToFile(file); + needsSaveToFile = false; + }; + } + + + // ========================> Helpers <======================== \\ + + public int GetAtlasIconCount() { return lastTransferedIconCount; } + + public long GetAtlasImageViewHandle() { return cvkAtlasImage.GetImageViewHandle(); } + + public long GetAtlasSamplerHandle() { return hAtlasSampler; } + + public int AddIcon(final String label) { + CVKAssertNotNull(label); + + if (label.isEmpty()) { + return TRANSPARENT_ICON_INDEX; + } + + // Use extended name to prevent duplication in the texture atlas + String extendedName = label; + final ConstellationIcon icon = IconManager.getIcon(extendedName); + if (icon != null) { + extendedName = icon.getExtendedName(); + } + + final Integer iconIndex = serializableData.loadedIcons.get(extendedName); + if (iconIndex == null) { + final int index = serializableData.loadedIcons.size(); + if (index >= serializableData.maxIcons) { + // Too many icons: return NOISE icon. + return NOISE_ICON_INDEX; + } + + serializableData.loadedIcons.put(extendedName, index); + return index; + } + + return iconIndex; + } + + public int SaveToFile(File file) { + int ret = cvkAtlasImage.SaveToFile(file); + needsSaveToFile = false; + return ret; + } + + /** + * Converts an icon index into column, row and layer indices + * + * Example where + * iconsPerRow = 4 + * rowsPerLayer = 5 + * iconsPerLayer = 20 + * + * 0 1 2 3 + * 4 5 6 7 + * 8 9 10 11 + * 12 13 14 15 + * 16 17 18 19 + * + * 20 21 22 23 + * 24 25 26 27 + * 28 29 30 31 + * 32 33 34 35 + * 36 37 38 39 + * + * 40 41 42 43 + * 44 45 46 47 + * 48 49 50 51 + * 52 53 54 55 + * 56 57 58 59 + * + * index = 27 + * x = 27 % 4 = 3 + * y = (27 % 20) / 4 = 1 + * z = 27 / 20 = 1 + * + * index = 38 + * x = 38 % 4 = 2 + * y = (38 % 20) / 4 = 4 + * z = 38 / 20 = 1 + * + * index = 48 + * x = 48 % 4 = 0 + * y = (48 % 20) / 4 = 2 + * z = 48 / 20 = 2 + * + * @param index + * @return vector of lookup indices + */ + public Vector3i IndexToTextureIndices(int index) { + return new Vector3i(index % serializableData.iconsPerRowColumn, + (index % serializableData.iconsPerLayer) / serializableData.iconsPerRowColumn, + index / serializableData.iconsPerLayer); + } + + public long IndexToBufferOffset(int index) { + long offset = 0; + Vector3i texIndices = IndexToTextureIndices(index); + + // add size of previous icons on this row + offset += texIndices.getX() * ICON_SIZE_BYTES; + + // add size of previous rows on this layer + offset += texIndices.getY() * serializableData.iconsPerRowColumn * ICON_SIZE_BYTES; + + // add size of previous layers + offset += texIndices.getZ() * serializableData.iconsPerLayer * ICON_SIZE_BYTES; + + int povCalc = index * ICON_SIZE_BYTES; + CVKAssert(offset == povCalc); + + return offset; + } + + class IconCopyWorker extends Thread { + private final IndexedConstellationIcon el; + private int ret = VK_SUCCESS; + + IconCopyWorker(final IndexedConstellationIcon el) { + this.el = el; + } + + @Override + public void run() { + ret = CopyConstellationIconToAtlas(el); + } + } + + private int CopyBufferedImageToAtlas(BufferedImage image, + Vector3i dstOffset, + Vector3i dstExtent, + int destSize, + boolean needsSwizzle) { + CVKAssertNotNull(image); + CVKAssertNotNull(image.getRaster()); + CVKAssertNotNull(image.getRaster().getDataBuffer()); + int ret = VK_SUCCESS; + + // Get source data as bytes + ByteBuffer pixels = null; + if (!needsSwizzle) { + final DataBuffer data = image.getRaster().getDataBuffer(); + if (data instanceof DataBufferByte) { + pixels = ByteBuffer.wrap(((DataBufferByte) data).getData()); + } else { + GetLogger().severe("data is not in byte form"); + return CVK_ERROR_ICON_ATLAS_UNSUPPORTED_ICON_FORMAT; + } + } else { + final int iconSizeBytes = ICON_COMPONENTS * image.getWidth() * image.getHeight(); + pixels = memAlloc(iconSizeBytes); + + // To save us having to swizzle with every render, do it now (ABGR->RGBA, AWT what were you thinking?) + for (int v = 0; v < image.getHeight(); ++v) { + for (int u = 0; u < image.getWidth(); ++u) { + Object o = image.getRaster().getDataElements(u, v, null); + final byte r = (byte) image.getColorModel().getRed(o); + final byte g = (byte) image.getColorModel().getGreen(o); + final byte b = (byte) image.getColorModel().getBlue(o); + final byte a = (byte) image.getColorModel().getAlpha(o); + final int offset = ICON_COMPONENTS * (u + image.getWidth() * v); + pixels.put(offset, r);//(byte) (agbr&0x000000FF)); + pixels.put(offset+1, g);//(byte)((agbr&0x00FF0000)>>16)); + pixels.put(offset+2, b);//(byte)((agbr&0x0000FF00)>>8)); + pixels.put(offset+3, a);//(byte) (agbr>>24)); + } + } + } + + // Create staging buffer (CPU writable, GPU readable) + int requiredBytes = dstExtent.getX() * dstExtent.getY() * ICON_COMPONENTS; + CVKBuffer stagingBuffer = CVKBuffer.Create(requiredBytes, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + null, + "CVKIconTextureAtlas cvkStagingBuffer"); + + // Copy pixels to staging buffer, note for undersized icons we need extra offsets to pad the top and sides + if (image.getWidth() == dstExtent.getX() && image.getHeight() == dstExtent.getY()) { + CVKAssert(pixels.capacity() == destSize); + stagingBuffer.Put(pixels, 0, 0, destSize); + } else { + // Zero this buffer so undersized icons are padded with transparent pixels + stagingBuffer.ZeroMemory(); + + // Offsets to centre the icon are in pixels + int colOffset = (dstExtent.getX() - image.getWidth()) / 2; + int rowOffset = (dstExtent.getY() - image.getHeight()) / 2; + + // Adjust the start position to the right row + for (int iRow = 0; iRow < image.getHeight(); ++iRow) { + // offset to the start of this row + int writePos = (iRow + rowOffset) * dstExtent.getX() * ICON_COMPONENTS; + CVKAssert(((iRow + rowOffset + 1) * dstExtent.getX() * ICON_COMPONENTS) <= destSize); + + // offset from the start of the row to the start of the icon + writePos += colOffset * ICON_COMPONENTS; + int readPos = iRow * image.getWidth() * ICON_COMPONENTS; + ret = stagingBuffer.Put(pixels, writePos, readPos, image.getWidth() * ICON_COMPONENTS); + if (VkFailed(ret)) { return ret; } + } + } + + synchronized(cvkAtlasImage) { + ret = cvkAtlasImage.CopyFrom(stagingBuffer, 0, dstOffset, dstExtent); + if (VkFailed(ret)) { return ret; } + } + + // If we swizzled before copying then we need to release the pixel data + // we allocated. + if (needsSwizzle && pixels != null) { + memFree(pixels); + } + + return ret; + } + + private int CopyConstellationIconToAtlas(final IndexedConstellationIcon el) { + BufferedImage iconImage = el.icon.buildBufferedImage(); + + // Convert the buffered image if its not in our desired state. + if (TYPE_4BYTE_ABGR != iconImage.getType()) { + BufferedImage convertedImg = new BufferedImage(iconImage.getWidth(), iconImage.getHeight(), BufferedImage.TYPE_INT_ARGB); + convertedImg.getGraphics().drawImage(iconImage, 0, 0, null); + iconImage = convertedImg; + } + int width = iconImage.getWidth(); + int height = iconImage.getHeight(); + CVKAssert(width <= ICON_DIMENSION); + CVKAssert(height <= ICON_DIMENSION); + + // Calculate offset into staging buffer for the current image layer + Vector3i texIndices = IndexToTextureIndices(el.index); + + // Copy it in. Note this is blocking. If this is highlighted as a performance issue use the + // add a version of CVKImage.CopyFrom that takes a command buffer but doesn't submit it and + // doesn't do any image layout transitions. + Vector3i dstOffset = new Vector3i(texIndices.getU() * ICON_DIMENSION, texIndices.getV() * ICON_DIMENSION, texIndices.getW()); + Vector3i dstExtent = new Vector3i(ICON_DIMENSION, ICON_DIMENSION, 1); + + return CopyBufferedImageToAtlas(iconImage, dstOffset, dstExtent, ICON_SIZE_BYTES, !RUNTIME_SWIZZLE); + } + + private int CreateAtlasSampler(MemoryStack stack) { + int ret; + + // Create a sampler to match the image. Note the sampler allows us to sample + // an image but isn't tied to a specific image, note the lack of image or + // imageview parameters below. + VkSamplerCreateInfo vkSamplerCreateInfo = VkSamplerCreateInfo.callocStack(stack); + vkSamplerCreateInfo.sType(VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO); + vkSamplerCreateInfo.maxAnisotropy(1.0f); + vkSamplerCreateInfo.magFilter(VK_FILTER_LINEAR); + vkSamplerCreateInfo.minFilter(VK_FILTER_LINEAR); + vkSamplerCreateInfo.mipmapMode(VK_SAMPLER_MIPMAP_MODE_LINEAR); + vkSamplerCreateInfo.addressModeU(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE); + vkSamplerCreateInfo.addressModeV(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE); + vkSamplerCreateInfo.addressModeW(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE); + vkSamplerCreateInfo.mipLodBias(0.0f); + vkSamplerCreateInfo.maxAnisotropy(8); + vkSamplerCreateInfo.compareOp(VK_COMPARE_OP_NEVER); + vkSamplerCreateInfo.minLod(0.0f); + vkSamplerCreateInfo.maxLod(0.0f); + vkSamplerCreateInfo.borderColor(VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE); + + LongBuffer pTextureSampler = stack.mallocLong(1); + ret = vkCreateSampler(CVKDevice.GetVkDevice(), vkSamplerCreateInfo, null, pTextureSampler); + if (VkFailed(ret)) { return ret; } + hAtlasSampler = pTextureSampler.get(0); + if (hAtlasSampler == VK_NULL_HANDLE) { return CVK_ERROR_ICON_ATLAS_SAMPLER_CREATION_FAILED; } + + return ret; + } + + // Only it's own function for measuring timing + private void ProcessIconCopying(final List icons, final ExecutorService pool) { + icons.stream().map(el -> new IconCopyWorker(el)).forEachOrdered(thread -> { + pool.execute(thread); + }); + } + + private int AddIconsToAtlas(final List icons) { + int ret; + + try (MemoryStack stack = stackPush()) { + // Sanity check duplicates + if (CVK_DEBUGGING) { + int dupcount = 0; + for (int i = 0; i < icons.size(); ++i) { + for (int j = i + 1; j < icons.size(); ++j) { + if (icons.get(i).icon.getExtendedName().equals(icons.get(j).icon.getExtendedName())) { + GetLogger().warning("%d Duplicate icon %s, %d (%s) and %d (%s)", + ++dupcount, icons.get(i).icon.getName(), + i, icons.get(i).icon.getExtendedName(), + j, icons.get(j).icon.getExtendedName()); + } + } + } + } + + int requiredLayers = (icons.size() / serializableData.iconsPerLayer) + 1; + + if (cvkAtlasImage != null) { + cvkAtlasImage.Destroy(); + cvkAtlasImage = null; + } + + // Control AGBR->RGBA swizzling in the image view + VkComponentMapping componentMapping = null; + if (RUNTIME_SWIZZLE) { + componentMapping = VkComponentMapping.callocStack(stack); + componentMapping.set(VK_COMPONENT_SWIZZLE_A, + VK_COMPONENT_SWIZZLE_B, + VK_COMPONENT_SWIZZLE_G, + VK_COMPONENT_SWIZZLE_R); + } + + + // Create destination image + cvkAtlasImage = CVKImage.Create(serializableData.texture2DDimension, + serializableData.texture2DDimension, + requiredLayers, + VK_FORMAT_R8G8B8A8_UNORM, //non-linear format to give more fidelity to the hues we are most able to perceive + VK_IMAGE_VIEW_TYPE_2D_ARRAY, //regardless how how many layers are in the image, the shaders that use that atlas use a sampler2DArray + VK_IMAGE_TILING_OPTIMAL, //we usually sample rectangles rather than long straight lines + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + VK_IMAGE_ASPECT_COLOR_BIT, + componentMapping, + null, + "CVKIconTextureAtlas cvkAtlasImage"); + + // Transition image from undefined to transfer destination optimal + ret = cvkAtlasImage.Transition(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + if (VkFailed(ret)) { return ret; } + + // Loop that copies icons from the icon manager into the atlas texture(s) + final ExecutorService pool = Executors.newFixedThreadPool(CVKUtils.NUM_CORES); + ProcessIconCopying(icons, pool); + + pool.shutdown(); + try { + pool.awaitTermination(1, TimeUnit.MINUTES); + } catch (InterruptedException e) { + GetLogger().LogException(e, "Timed out copying icons into CVKIconTextureAtlas"); + return CVK_ERROR_ICON_ATLAS_COPY_TIMEDOUT; + } + + // Now the image is populated, transition it for reading + ret = cvkAtlasImage.Transition(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + if (VkFailed(ret)) { return ret; } + + // TODO: rethink when to cache. + // Immedate cache when the atlas is updated. This could be a real PIA + // if the user is adding one new icon over and over to an already + // large atlas. + if (CVK_ICON_ATLAS_CACHING) { + CacheAtlas(); + } + } + + return ret; + } + + /** + * For large atlases, processing individual icons and copying them one by one + * into the atlas layers is very slow. This method writes the layers, sizes + * and loadedIcons map to disk. When the atlas is first created it can load + * this much faster than it takes to create from source icons. + * + * TODO: make these paths resolve to actual user directories like %APPDATA% on Windows + * TODO: for that matter, locally compiled shaders and the render.conf should + * probably be in %APPDATA% or platform equivalent. + */ + private void CacheAtlas() { + CVKAssert(CVK_ICON_ATLAS_CACHING); + + // Get our atlas layers in a serializable bufferImage list + cvkAtlasImage.SaveToBufferedImages(serializableData.layers); + + // Delete old cache + Path cacheFolderPath = Paths.get(Places.getUserDirectory().getPath(), "../../cache/").toAbsolutePath().normalize(); + File cacheFolder = cacheFolderPath.toFile(); + if (!cacheFolder.exists()) { + cacheFolder.mkdirs(); + } + + // Delete old cache file + File cacheFile = cacheFolderPath.resolve("icon.atlas").toFile(); + try { + cacheFile.delete(); + } catch (Exception e) { + // old cache doesn't exist or is locked, oh well keep going + } + + // Write new cache file + try { + GetLogger().info("Caching %d icons to %s", serializableData.loadedIcons.size(), cacheFile.toString()); + FileOutputStream fileOut = new FileOutputStream(cacheFile); + ObjectOutputStream out = new ObjectOutputStream(fileOut); + out.writeObject(serializableData); + out.close(); + fileOut.close(); + } catch (Exception e) { + GetLogger().LogException(e, "Exception trying to cache icon atlas to %s", cacheFile.toString()); + } + + // Clear the layers as these are only needed during the caching to disk + // and between caching from disk and copying to the atlas texture. + serializableData.layers.clear(); + } + + private void LoadAtlasCache() { + CVKAssert(CVK_ICON_ATLAS_CACHING); + + if (!cacheLoaded) { + // Serialize the data from disk + File cacheFile = Paths.get(Places.getUserDirectory().getPath(), "../../cache/", "icon.atlas").toAbsolutePath().normalize().toFile(); + if (cacheFile.exists()) { + try { + FileInputStream fileIn = new FileInputStream(cacheFile); + ObjectInputStream in = new ObjectInputStream(fileIn); + serializableData = (CVKIconAtlasSerializable)in.readObject(); + in.close(); + fileIn.close(); + } catch (Exception e) { + GetLogger().LogException(e, "Exception trying to cache icon atlas"); + } + + // TODO: improve this serialisation. In testing when I forced 512px + // images (149 layers) only every 4th image serialised (and I didn't + // validate the contents were correct). So for now check and reset + // if we have any failed layers. + for (BufferedImage image : serializableData.layers) { + if (image == null) { + serializableData.Reset(); + GetLogger().severe("Error serialising icon texture atlas."); + break; + } + } + } + + + // TODO: check the dimensions are acceptable for this gpu. The user + // could have copied the cache from a different machine or changed + // their driver or hardware. If not, reset serialisableData. + } + } + + + private CVKGraphLogger GetLogger() { return CVKGraphLogger.GetStaticLogger(); } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKImage.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKImage.java new file mode 100644 index 0000000000..846f433305 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKImage.java @@ -0,0 +1,1085 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.resourcetypes; + +import au.gov.asd.tac.constellation.utilities.graphics.Vector3i; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKFormatUtils; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_ERROR_BUFFER_TOO_SMALL_FOR_COPY; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_ERROR_IMAGE_TOO_SMALL_FOR_COPY; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_ERROR_INVALID_ARGS; +import java.nio.LongBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import static org.lwjgl.vulkan.VK10.VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; +import static org.lwjgl.vulkan.VK10.VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; +import static org.lwjgl.vulkan.VK10.VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT; +import static org.lwjgl.vulkan.VK10.VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; +import static org.lwjgl.vulkan.VK10.VK_ACCESS_SHADER_READ_BIT; +import static org.lwjgl.vulkan.VK10.VK_ACCESS_TRANSFER_WRITE_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_ASPECT_COLOR_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_ASPECT_DEPTH_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_ASPECT_STENCIL_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_UNDEFINED; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_TYPE_1D; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_TYPE_2D; +import static org.lwjgl.vulkan.VK10.VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; +import static org.lwjgl.vulkan.VK10.VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; +import static org.lwjgl.vulkan.VK10.VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; +import static org.lwjgl.vulkan.VK10.VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; +import static org.lwjgl.vulkan.VK10.VK_PIPELINE_STAGE_TRANSFER_BIT; +import static org.lwjgl.vulkan.VK10.VK_QUEUE_FAMILY_IGNORED; +import static org.lwjgl.vulkan.VK10.VK_SAMPLE_COUNT_1_BIT; +import static org.lwjgl.vulkan.VK10.VK_SHARING_MODE_EXCLUSIVE; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; +import static org.lwjgl.vulkan.VK10.VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; +import static org.lwjgl.vulkan.VK10.VK_SUCCESS; +import static org.lwjgl.vulkan.VK10.vkAllocateMemory; +import static org.lwjgl.vulkan.VK10.vkCreateImageView; +import static org.lwjgl.vulkan.VK10.vkBindImageMemory; +import static org.lwjgl.vulkan.VK10.vkCmdCopyBufferToImage; +import static org.lwjgl.vulkan.VK10.vkCmdPipelineBarrier; +import static org.lwjgl.vulkan.VK10.vkCreateImage; +import static org.lwjgl.vulkan.VK10.vkDestroyImage; +import static org.lwjgl.vulkan.VK10.vkDestroyImageView; +import static org.lwjgl.vulkan.VK10.vkFreeMemory; +import static org.lwjgl.vulkan.VK10.vkGetImageMemoryRequirements; +import org.lwjgl.vulkan.VkBufferImageCopy; +import org.lwjgl.vulkan.VkExtent3D; +import org.lwjgl.vulkan.VkImageCreateInfo; +import org.lwjgl.vulkan.VkImageMemoryBarrier; +import org.lwjgl.vulkan.VkImageViewCreateInfo; +import org.lwjgl.vulkan.VkMemoryAllocateInfo; +import org.lwjgl.vulkan.VkMemoryRequirements; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_ERROR_INVALID_IMAGE; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkFailed; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_LEVEL_PRIMARY; +import static org.lwjgl.vulkan.VK10.VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKFormatUtils.VkFormatByteDepth; +import au.gov.asd.tac.constellation.visual.vulkan.utils.CVKGraphLogger; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssert; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssertNotNull; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVKAssertNull; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_ALLOCATION_LOG_LEVEL; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_DEBUGGING; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_ERROR_DEST_IMAGE_CREATE_FAILED; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_ERROR_IMAGE_GET_ID_FAILED; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_ERROR_IMAGE_VIEW_CREATION_FAILED; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_ERROR_SAVE_TO_FILE_FAILED; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_VKALLOCATIONS; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.VkSucceeded; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.checkVKret; +import java.awt.Point; +import java.awt.Transparency; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; +import java.awt.image.BufferedImage; +import java.awt.image.ColorModel; +import java.awt.image.ComponentColorModel; +import java.awt.image.DataBuffer; +import java.awt.image.DataBufferByte; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; +import java.io.File; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import javax.imageio.ImageIO; +import org.apache.commons.io.FilenameUtils; +import org.lwjgl.PointerBuffer; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.vulkan.KHRSwapchain.VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; +import static org.lwjgl.vulkan.VK10.VK_ACCESS_MEMORY_READ_BIT; +import static org.lwjgl.vulkan.VK10.VK_ACCESS_TRANSFER_READ_BIT; +import static org.lwjgl.vulkan.VK10.VK_FILTER_NEAREST; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8A8_UNORM; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_GENERAL; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_TILING_LINEAR; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_USAGE_SAMPLED_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_USAGE_TRANSFER_DST_BIT; +import static org.lwjgl.vulkan.VK10.VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; +import static org.lwjgl.vulkan.VK10.VK_WHOLE_SIZE; +import static org.lwjgl.vulkan.VK10.vkCmdBlitImage; +import static org.lwjgl.vulkan.VK10.vkCmdCopyImage; +import static org.lwjgl.vulkan.VK10.vkGetImageSubresourceLayout; +import static org.lwjgl.vulkan.VK10.vkMapMemory; +import static org.lwjgl.vulkan.VK10.vkUnmapMemory; +import org.lwjgl.vulkan.VkComponentMapping; +import org.lwjgl.vulkan.VkImageBlit; +import org.lwjgl.vulkan.VkImageCopy; +import org.lwjgl.vulkan.VkImageSubresource; +import org.lwjgl.vulkan.VkOffset3D; +import org.lwjgl.vulkan.VkSubresourceLayout; + + +public class CVKImage { + protected CVKGraphLogger cvkGraphLogger = null; + protected LongBuffer pImage = MemoryUtil.memAllocLong(1); + protected LongBuffer pImageView = MemoryUtil.memAllocLong(1); + protected LongBuffer pImageMemory = MemoryUtil.memAllocLong(1); + protected int width = 0; + protected int height = 0; + protected int layers = 0; + protected int format = 0; + protected int tiling = 0; + protected int usage = 0; + protected int properties = 0; + protected int aspectMask = 0; + protected int imageType = 0; + protected int viewType = 0; + protected int layout = 0; + protected String DEBUGNAME = ""; + protected PointerBuffer pWriteMemory = null; + private boolean needsSwizzle = false; + + protected CVKImage() {} + + public boolean IsValid() { return pImage != null && pImageView != null && pImageMemory != null; } + public long GetImageHandle() { return pImage != null ? pImage.get(0) : VK_NULL_HANDLE; } + public long GetImageViewHandle() { return pImageView != null ? pImageView.get(0) : VK_NULL_HANDLE; } + public long GetMemoryImageHandle() { return pImageMemory != null ? pImageMemory.get(0) : VK_NULL_HANDLE; } + public int GetFormat() { return format; } + public int GetAspectMask() { return aspectMask; } + private CVKGraphLogger GetLogger() { return cvkGraphLogger != null ? cvkGraphLogger : CVKGraphLogger.GetStaticLogger(); } + public int GetLayout() { return layout; } + public void SetLayout(int layout) { this.layout = layout; } + + public void Destroy() { + DestroyImage(); + DestroyImageView(); + DestroyImageMemory(); + } + + public void DestroyImage() { + if (CVK_DEBUGGING && pImage.get(0) != VK_NULL_HANDLE) { + final CVKGraphLogger logger = GetLogger(); + if (logger.isLoggable(CVK_ALLOCATION_LOG_LEVEL)) { + if (pImageMemory != null && pImageMemory.get(0) != VK_NULL_HANDLE) { + --CVK_VKALLOCATIONS; + logger.log(CVK_ALLOCATION_LOG_LEVEL, "CVK_VKALLOCATIONS (%d-) Destroy called on CVKimage %s (image:0x%016X memory:0x%016X), vkFreeMemory will be called", + CVK_VKALLOCATIONS, DEBUGNAME, pImage.get(0), pImageMemory.get(0)); + } else { + logger.log(CVK_ALLOCATION_LOG_LEVEL, "CVK_VKALLOCATIONS (%d!) Destroy called on CVKimage %s (image:0x%016X memory:0x%016X), vkFreeMemory will NOT be called", + CVK_VKALLOCATIONS, DEBUGNAME, pImage.get(0), pImageMemory.get(0)); + } + } + } + if (pImage.get(0) != VK_NULL_HANDLE) { + vkDestroyImage(CVKDevice.GetVkDevice(), pImage.get(0), null); + pImage.put(0, VK_NULL_HANDLE); + pImage = null; + } + } + + public void DestroyImageView() { + if (pImageView.get(0) != VK_NULL_HANDLE) { + GetLogger().info("Destroying VkImageView:0x%016X for %s)", pImageView.get(0), DEBUGNAME); + vkDestroyImageView(CVKDevice.GetVkDevice(), pImageView.get(0), null); + pImageView.put(0, VK_NULL_HANDLE); + pImageView = null; + } + } + + public void DestroyImageMemory() { + if (pImageMemory.get(0) != VK_NULL_HANDLE) { + vkFreeMemory(CVKDevice.GetVkDevice(), pImageMemory.get(0), null); + pImageMemory.put(0, VK_NULL_HANDLE); + pImageMemory = null; + } + } + + @SuppressWarnings("deprecation") + @Override + public void finalize() throws Throwable { + Destroy(); + super.finalize(); + } + + + public int Transition(int newLayout) { + int ret; + + CVKCommandBuffer cvkTransitionCmd = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_PRIMARY, cvkGraphLogger, "CVKImage.Transition cvkTransitionCmd"); + ret = cvkTransitionCmd.Begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT); + if (VkFailed(ret)) { return ret; } + ret = Transition(cvkTransitionCmd, newLayout); + if (VkSucceeded(ret)) { + // Blocking execute the command buffer + ret = cvkTransitionCmd.EndAndSubmit(); + } + cvkTransitionCmd.Destroy(); + + return ret; + } + + + /** + * This method transitions an image from layout to another. It uses a memory + * barrier to do this. It doesn't sound like the kind of construct that is + * implicitly a command, but as the Vulkan-Tutorial (Images) says: + * "One of the most common ways to perform layout transitions is using an image + * memory barrier. A pipeline barrier like that is generally used to synchronize + * access to resources, like ensuring that a write to a buffer completes before + * reading from it..." + * + * Some member variables aren't set until an image is transitioned for the first + * time, TODO add asserts to catch access before transition? + * + * @param cvkCmdBuf + * @param newLayout + * @return + */ + public int Transition(CVKCommandBuffer cvkCmdBuf, int newLayout) { + int ret = VK_SUCCESS; + + try(MemoryStack stack = stackPush()) { + VkImageMemoryBarrier.Buffer vkBarrier = VkImageMemoryBarrier.callocStack(1, stack); + vkBarrier.sType(VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER); + vkBarrier.oldLayout(layout); + vkBarrier.newLayout(newLayout); + vkBarrier.srcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED); + vkBarrier.dstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED); + vkBarrier.image(GetImageHandle()); + + vkBarrier.subresourceRange().baseMipLevel(0); + vkBarrier.subresourceRange().levelCount(1); + vkBarrier.subresourceRange().baseArrayLayer(0); + vkBarrier.subresourceRange().layerCount(1); + + // Depth/stencil or colour? + if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; + if (CVKFormatUtils.VkFormatHasStencilComponent(format)) { + aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + } else { + aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + } + vkBarrier.subresourceRange().aspectMask(aspectMask); + + int sourceStage; + int destinationStage; + if (layout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + vkBarrier.srcAccessMask(0); + vkBarrier.dstAccessMask(VK_ACCESS_TRANSFER_WRITE_BIT); + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else if(/*layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL &&*/ newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { + vkBarrier.srcAccessMask(VK_ACCESS_TRANSFER_WRITE_BIT); + vkBarrier.dstAccessMask(VK_ACCESS_SHADER_READ_BIT); + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + } else if (layout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { + vkBarrier.srcAccessMask(0); + vkBarrier.dstAccessMask(VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT); + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; + } else if(layout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { + vkBarrier.srcAccessMask(0); + vkBarrier.dstAccessMask(VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT); + sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + destinationStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + // Tansition image into layout for copying (e.g. copy to memory or screenshot) + } else if( newLayout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) { + vkBarrier.srcAccessMask(VK_ACCESS_TRANSFER_WRITE_BIT); + vkBarrier.dstAccessMask(VK_ACCESS_MEMORY_READ_BIT); + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + // Transition copy image back to general state + } else if(layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_GENERAL) { + vkBarrier.srcAccessMask(VK_ACCESS_MEMORY_READ_BIT); + vkBarrier.dstAccessMask(VK_ACCESS_TRANSFER_READ_BIT); + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + // Transition swapchain image back to original state after a screenshot + } else if(layout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL && (newLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR || + newLayout == VK_IMAGE_LAYOUT_GENERAL)) { + vkBarrier.srcAccessMask(VK_ACCESS_MEMORY_READ_BIT); + vkBarrier.dstAccessMask(VK_ACCESS_TRANSFER_READ_BIT); + sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; + } else { + throw new IllegalArgumentException("Unsupported layout transition"); + } + + for (int iLayer = 0; iLayer < layers; ++iLayer) { + vkBarrier.subresourceRange().baseArrayLayer(iLayer); + vkCmdPipelineBarrier(cvkCmdBuf.GetVKCommandBuffer(), + sourceStage, + destinationStage, + 0, // dependency flags + null, // memory barriers + null, // buffer memory barriers + vkBarrier); // image memory barriers + } + } + layout = newLayout; + + return ret; + } + + + public int CopyFrom(CVKBuffer buffer) { + Vector3i dstPixelOffsets = new Vector3i(0,0,0); + Vector3i dstPixelExtents = new Vector3i(0,0,0); + + // Sanity check the buffer is probably holding pixels that match our format + if ((buffer.GetBufferSize() % VkFormatByteDepth(format)) != 0) { + return CVK_ERROR_INVALID_ARGS; + } + + // Check for overflow from ridiculously large buffer + if (buffer.GetBufferSize() > Integer.MAX_VALUE) { + return CVK_ERROR_INVALID_ARGS; + } + + // Calculate width + int pixelsInBuffer = (int)buffer.GetBufferSize() / VkFormatByteDepth(format); + dstPixelExtents.setX(Math.min(pixelsInBuffer, width)); + + // Calculate height + int rowsInBuffer = pixelsInBuffer / width; + int rowRemainder = pixelsInBuffer % width; + if (rowRemainder > 0) { + ++rowsInBuffer; + } + dstPixelExtents.setY(Math.min(rowsInBuffer, height)); + + // Calculate depth + int pixelsInImageLayer = width * height; + int requiredLayers = pixelsInBuffer / pixelsInImageLayer; + int leftOverPixels = pixelsInBuffer % pixelsInImageLayer; + if (leftOverPixels > 0) { + ++requiredLayers; + + // If this is a 1D array image we can treat it like a buffer, but if + // it is 2D then we must either fill a region smaller than a full + // layer or we fill full layers. + if (layout == VK_IMAGE_TYPE_2D) { + return CVK_ERROR_INVALID_ARGS; + } + } + if (requiredLayers > layers) { + return CVK_ERROR_IMAGE_TOO_SMALL_FOR_COPY; + } + dstPixelExtents.setZ(requiredLayers); + + return CopyFrom(buffer, 0, dstPixelOffsets, dstPixelExtents); + } + + + public int CopyFrom(CVKBuffer buffer, final int srcByteOffset, final Vector3i dstPixelOffset, final Vector3i dstPixelExtent) { + int ret; + + // Sanity check our image is valid + if (layout == VK_IMAGE_LAYOUT_UNDEFINED || width == 0 || height == 0 || layers == 0) { + return CVK_ERROR_INVALID_IMAGE; + } + + // Sanity check we have something to copy + if (dstPixelExtent.isZero()) { + return CVK_ERROR_INVALID_ARGS; + } + + // only allow incomplete writes into a single layer + if (!dstPixelOffset.isZero() && dstPixelExtent.getZ() != 1) { + return CVK_ERROR_INVALID_ARGS; + } + + // Check the destination region is within our extents + if (width < (dstPixelOffset.getX() + dstPixelExtent.getX())) { + return CVK_ERROR_IMAGE_TOO_SMALL_FOR_COPY; + } + if (height < (dstPixelOffset.getY() + dstPixelExtent.getY())) { + return CVK_ERROR_IMAGE_TOO_SMALL_FOR_COPY; + } + if (layers < (dstPixelOffset.getZ() + dstPixelExtent.getZ())) { + return CVK_ERROR_IMAGE_TOO_SMALL_FOR_COPY; + } + + // Validate the buffer contains enough bytes to fullful the destination extents + int bytesRequired = dstPixelExtent.getX() * dstPixelExtent.getY() * dstPixelExtent.getZ() * VkFormatByteDepth(format); + if (bytesRequired > (buffer.GetBufferSize() - srcByteOffset)) { + return CVK_ERROR_BUFFER_TOO_SMALL_FOR_COPY; + } + + // Command to copy pixels and potentially transition if necessary + CVKCommandBuffer cvkCopyCmd = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_PRIMARY, cvkGraphLogger, "CVKImage.CopyFrom cvkCopyCmd"); + ret = cvkCopyCmd.Begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT); + if (VkFailed(ret)) { return ret; } + + // Handle image transition if needed + int originalLayout = layout; + if (layout != VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { + ret = Transition(cvkCopyCmd, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + if (VkFailed(ret)) { return ret; } + } + + // Copy each layer + try (MemoryStack stack = stackPush()) { + for (int iLayer = 0; iLayer < dstPixelExtent.getZ(); ++iLayer) { + // Setup a buffer image copy structure for the current image layer + VkBufferImageCopy.Buffer copyLayerBuffer = VkBufferImageCopy.callocStack(1, stack); + VkBufferImageCopy copyLayer = copyLayerBuffer.get(0); + copyLayer.bufferOffset(srcByteOffset); + copyLayer.bufferRowLength(0); // Tightly packed + copyLayer.bufferImageHeight(0); // Tightly packed + copyLayer.imageSubresource().aspectMask(VK_IMAGE_ASPECT_COLOR_BIT); + copyLayer.imageSubresource().mipLevel(0); + copyLayer.imageSubresource().baseArrayLayer(dstPixelOffset.getZ() + iLayer); + copyLayer.imageSubresource().layerCount(1); + copyLayer.imageOffset().set(dstPixelOffset.getX(), dstPixelOffset.getY(), 0); + copyLayer.imageExtent(VkExtent3D.callocStack(stack).set(dstPixelExtent.getX(), dstPixelExtent.getY(), 1)); + + // Enqueue the copy command to the command buffer + vkCmdCopyBufferToImage(cvkCopyCmd.GetVKCommandBuffer(), + buffer.GetBufferHandle(), + GetImageHandle(), + layout, + copyLayerBuffer); + } + } + + // Restore image layout if needed + if (layout != originalLayout) { + ret = Transition(cvkCopyCmd, originalLayout); + if (VkFailed(ret)) { return ret; } + } + + // Blocking execute the command buffer + ret = cvkCopyCmd.EndAndSubmit(); + if (VkFailed(ret)) { return ret; } + + cvkCopyCmd.Destroy(); + + return ret; + } + + + /** + * Factory returning an Image that has been allocated and an ImageView + * created with it. + * + * @param width + * @param height + * @param layers + * @param format + * @param viewType + * @param tiling + * @param usage + * @param properties + * @param aspectMask + * @param componentMapping + * @param graphLogger + * @param debugName + * @return The image or null if errors occur + */ + public static CVKImage Create( int width, + int height, + int layers, + int format, + int viewType, + int tiling, + int usage, + int properties, + int aspectMask, + VkComponentMapping componentMapping, + CVKGraphLogger graphLogger, + String debugName) { + CVKImage cvkImage = CreateImage(width, + height, + layers, + format, + viewType, + tiling, + usage, + properties, + aspectMask, + graphLogger, + debugName); + + cvkImage.CreateImageView(componentMapping); + + return cvkImage; + + } + + + /** + * Factory function that creates/allocates memory for an image + * + * @param width + * @param height + * @param layers + * @param format + * @param viewType + * @param tiling + * @param usage + * @param properties + * @param aspectMask + * @param graphLogger + * @param debugName + * @return The image or null if errors occur + */ + public static CVKImage CreateImage(int width, + int height, + int layers, + int format, + int viewType, + int tiling, + int usage, + int properties, + int aspectMask, + CVKGraphLogger graphLogger, + String debugName) { + CVKAssertNotNull(CVKDevice.GetVkDevice()); + CVKAssert(layers >= 1); + + int ret; + CVKImage cvkImage = new CVKImage(); + cvkImage.width = width; + cvkImage.height = height; + cvkImage.layers = layers; + cvkImage.format = format; + cvkImage.tiling = tiling; + cvkImage.usage = usage; + cvkImage.properties = properties; + cvkImage.aspectMask = aspectMask; + cvkImage.imageType = height > 1 ? VK_IMAGE_TYPE_2D : VK_IMAGE_TYPE_1D; + cvkImage.viewType = viewType; + cvkImage.layout = VK_IMAGE_LAYOUT_UNDEFINED; + cvkImage.cvkGraphLogger = graphLogger; + cvkImage.DEBUGNAME = debugName; + + try(MemoryStack stack = stackPush()) { + // Create the image, this is an opaque type we can't read or write + VkImageCreateInfo vkImageInfo = VkImageCreateInfo.callocStack(stack); + vkImageInfo.sType(VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO); + vkImageInfo.imageType(cvkImage.imageType); + vkImageInfo.extent().width(width); + vkImageInfo.extent().height(height); + vkImageInfo.extent().depth(1); + vkImageInfo.mipLevels(1); + vkImageInfo.arrayLayers(layers); + vkImageInfo.format(format); + vkImageInfo.tiling(tiling); + vkImageInfo.initialLayout(cvkImage.layout); + vkImageInfo.usage(usage); + vkImageInfo.samples(VK_SAMPLE_COUNT_1_BIT); + vkImageInfo.sharingMode(VK_SHARING_MODE_EXCLUSIVE); + + ret = vkCreateImage(CVKDevice.GetVkDevice(), vkImageInfo, null, cvkImage.pImage); + if (VkFailed(ret)) { return null; } + + CVKAssertNotNull(cvkImage.pImage.get(0)); + + // The image isn't backed by memory, do that now + VkMemoryRequirements vkMemoryRequirements = VkMemoryRequirements.mallocStack(stack); + vkGetImageMemoryRequirements(CVKDevice.GetVkDevice(), cvkImage.pImage.get(0), vkMemoryRequirements); + + VkMemoryAllocateInfo allocInfo = VkMemoryAllocateInfo.callocStack(stack); + allocInfo.sType(VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO); + allocInfo.allocationSize(vkMemoryRequirements.size()); + allocInfo.memoryTypeIndex(CVKDevice.GetMemoryType(vkMemoryRequirements.memoryTypeBits(), properties)); + + if (vkAllocateMemory(CVKDevice.GetVkDevice(), allocInfo, null, cvkImage.pImageMemory) != VK_SUCCESS) { + throw new RuntimeException("Failed to allocate image memory"); + } + if (CVK_DEBUGGING) { + final CVKGraphLogger logger = cvkImage.GetLogger(); + if (logger.isLoggable(CVK_ALLOCATION_LOG_LEVEL)) { + ++CVK_VKALLOCATIONS; + logger.log(CVK_ALLOCATION_LOG_LEVEL, "CVK_VKALLOCATIONS(%d+) vkAllocateMemory(%d) for CVKimage %s (image:0x%016X memory:0x%016X)", + CVK_VKALLOCATIONS, vkMemoryRequirements.size(), cvkImage.DEBUGNAME, cvkImage.pImage.get(0), cvkImage.pImageMemory.get(0)); + } + } + + vkBindImageMemory(CVKDevice.GetVkDevice(), cvkImage.pImage.get(0), cvkImage.pImageMemory.get(0), 0); + } + return cvkImage; + } + + + /** + * Creates an ImageView associate with the image handle + * @param componentMapping + * @return error code + */ + public int CreateImageView(VkComponentMapping componentMapping) { + int ret = VK_SUCCESS; + + try(MemoryStack stack = stackPush()) { + // Create an image view that can be used to read and write this image + VkImageViewCreateInfo vkViewInfo = VkImageViewCreateInfo.callocStack(stack); + vkViewInfo.sType(VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO); + vkViewInfo.image(GetImageHandle()); + vkViewInfo.viewType(viewType); + vkViewInfo.format(GetFormat()); + vkViewInfo.subresourceRange().aspectMask(GetAspectMask()); + vkViewInfo.subresourceRange().baseMipLevel(0); + vkViewInfo.subresourceRange().levelCount(1); + vkViewInfo.subresourceRange().baseArrayLayer(0); + vkViewInfo.subresourceRange().layerCount(layers); + + if (componentMapping != null) { + vkViewInfo.components().set(componentMapping); + } + + ret = vkCreateImageView(CVKDevice.GetVkDevice(), vkViewInfo, null, pImageView); + if (VkFailed(ret)) { return ret; } + + GetLogger().info("Created VkImageView:0x%016X for %s)", pImageView.get(0), DEBUGNAME); + + } catch (Exception e) { + GetLogger().LogException(e, "Image View creation failed for Image Handle %d. Image will be destroyed!", GetImageHandle()); + ret = CVK_ERROR_IMAGE_VIEW_CREATION_FAILED; + + DestroyImage(); + DestroyImageMemory(); + } + + return ret; + } + + + /** + * Copies this image into destImage in CPU memory to the VK_FORMAT_R8G8B8A8_UNORM + * format + * + * @param stack + * @return error code + */ + private int CopyToCPUMemoryImage(MemoryStack stack, AtomicReference destImage) { + CVKAssertNull(destImage.get()); + + int ret = VK_SUCCESS; + boolean supportsBlit = true; + int originalLayout = VK_IMAGE_LAYOUT_UNDEFINED; + + // Check blit support for source and destination + supportsBlit = CVKDevice.GetInstance().CheckDeviceSupportsBlit(format, VK_FORMAT_R8G8B8A8_UNORM); + + // Source for the copy is the last rendered swapchain image + originalLayout = layout; + CVKImage srcImage = this; + + // Create the linear tiled destination image to copy to and to read the memory from + destImage.set(CVKImage.Create(width, + height, + layers, + VK_FORMAT_R8G8B8A8_UNORM, + VK_IMAGE_TYPE_2D, + VK_IMAGE_TILING_LINEAR, + VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, + aspectMask, + null, + cvkGraphLogger, + "CVKImage destImage")); + if (destImage.get() == null) { return CVK_ERROR_DEST_IMAGE_CREATE_FAILED; } + + // Do the actual blit from the swapchain image to our host visible destination image + CVKCommandBuffer cvkCopyCmd = CVKCommandBuffer.Create(VK_COMMAND_BUFFER_LEVEL_PRIMARY, GetLogger(), "CVKImage CopyCmdBuffer"); + + ret = cvkCopyCmd.Begin(VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT); + if (VkFailed(ret)) { return ret; } + + // Transition destination image to transfer destination layout + ret = destImage.get().Transition(cvkCopyCmd, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); + if (VkFailed(ret)) { return ret; } + + // Transition source image from present to transfer source layout + ret = srcImage.Transition(cvkCopyCmd, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL); + if (VkFailed(ret)) { return ret; } + + // If source and destination support blit we'll blit as this also does automatic format conversion (e.g. from BGR to RGB) + needsSwizzle = false; + if (supportsBlit) { + // Define the region to blit (we will blit the whole swapchain image) + VkOffset3D blitSize = VkOffset3D.callocStack(stack); + blitSize.set(width, height, layers); + VkImageBlit.Buffer imageBlitRegion = VkImageBlit.callocStack(1, stack); + imageBlitRegion.srcSubresource().aspectMask(VK_IMAGE_ASPECT_COLOR_BIT); + imageBlitRegion.srcSubresource().layerCount(layers); + imageBlitRegion.srcOffsets(1, blitSize); + imageBlitRegion.dstSubresource().aspectMask(VK_IMAGE_ASPECT_COLOR_BIT); + imageBlitRegion.dstSubresource().layerCount(layers); + imageBlitRegion.dstOffsets(1, blitSize); + + // Issue the blit command + // The blit will automatically transfer from the original format + // to the destination. For example the swap image uses RGB + // while the destination format is VK_FORMAT_R8G8B8A8_UNORM + vkCmdBlitImage( + cvkCopyCmd.GetVKCommandBuffer(), + srcImage.GetImageHandle(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + destImage.get().GetImageHandle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + imageBlitRegion, + VK_FILTER_NEAREST); + } + else + { + // Otherwise use image copy (requires us to manually flip components) + VkImageCopy.Buffer imageCopyRegion = VkImageCopy.callocStack(1, stack); + imageCopyRegion.srcSubresource().aspectMask(VK_IMAGE_ASPECT_COLOR_BIT); + imageCopyRegion.srcSubresource().layerCount(layers); + imageCopyRegion.dstSubresource().aspectMask(VK_IMAGE_ASPECT_COLOR_BIT); + imageCopyRegion.dstSubresource().layerCount(layers); + + VkExtent3D extent3D = VkExtent3D.callocStack(stack); + extent3D.set(width, height, layers); + imageCopyRegion.extent(extent3D); + + // Issue the copy command + vkCmdCopyImage( + cvkCopyCmd.GetVKCommandBuffer(), + srcImage.GetImageHandle(), VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + destImage.get().GetImageHandle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + imageCopyRegion); + + destImage.get().needsSwizzle = true; + } + + // Transition destination image to general layout, which is the required layout for mapping the image memory later on + ret = destImage.get().Transition(cvkCopyCmd, VK_IMAGE_LAYOUT_GENERAL); + if (VkFailed(ret)) { return ret; } + + // Transition back the image to original layout after the blit is done + ret = srcImage.Transition(cvkCopyCmd, originalLayout); + if (VkFailed(ret)) { return ret; } + + // Submit the commands and wait + ret = cvkCopyCmd.EndAndSubmit(); + if (VkFailed(ret)) { return ret; } + + return ret; + } + + /** + * Maps the images memory into a buffer + * + * @param stack + * @return buffer filled with memory + */ + private int StartMemoryMap(MemoryStack stack, int layer) { + CVKAssert(pWriteMemory == null); + + // Get layout of the image to determine the offset + VkImageSubresource subResource = VkImageSubresource.callocStack(stack); + subResource.set(VK_IMAGE_ASPECT_COLOR_BIT, 0, layer); + VkSubresourceLayout subResourceLayout = VkSubresourceLayout.callocStack(stack); + vkGetImageSubresourceLayout(CVKDevice.GetVkDevice(), GetImageHandle(), subResource, subResourceLayout); + + // Map Memory + pWriteMemory = MemoryUtil.memAllocPointer(1); + long offset = subResourceLayout.offset(); + + return vkMapMemory(CVKDevice.GetVkDevice(), GetMemoryImageHandle(), offset, VK_WHOLE_SIZE, 0, pWriteMemory); + } + + + /** + * Cleanup the mapped memory and from StartMemoryMap + */ + private void EndMemoryMap() { + CVKAssertNotNull(pWriteMemory); + vkUnmapMemory(CVKDevice.GetVkDevice(), GetMemoryImageHandle()); + MemoryUtil.memFree(pWriteMemory); + pWriteMemory = null; + } + + + /** + * SaveMemoryToFile + * + * Takes the imageSource, that has already been transitioned to CPU memory, + * and saves it to file in the PNG format. + * + * @param stack + * @param file + * @return + */ + private int SaveImageToFile(MemoryStack stack, File file) { + CVKAssertNotNull(file); + int ret = VK_SUCCESS; + + File outFile = file; + List bufferedImages = new ArrayList<>(); + ret = SaveImageToMemory(stack, bufferedImages, false); + if (VkFailed(ret)) return ret; + CVKAssert(bufferedImages.size() == layers); + + for (int layer = 0; layer < layers; ++layer) { + if (layers > 1) { + final String extension = FilenameUtils.getExtension(file.getName()); + final String baseName = FilenameUtils.getBaseName(file.getAbsolutePath()); + final String path = FilenameUtils.getFullPath(file.getAbsolutePath()); + final String fileName = String.format("%s%s_%d.%s", + path, + baseName, + layer, + extension); + outFile = new File(fileName); + } + + // Save to file + try { + ImageIO.write(bufferedImages.get(layer), "png", outFile); + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + GetLogger().LogException(e, "Save file failed: %s", outFile.getName()); + return CVK_ERROR_SAVE_TO_FILE_FAILED; + } + } + + return ret; + } + + /** + * + * @param stack + * @param bufferedImages + * @param writeRaw: when true don't do any mutation of the source texels as + * this data is intended to be read back directly into a + * new CVKImage. + * @return + */ + public int SaveImageToMemory(MemoryStack stack, List bufferedImages, boolean writeRaw) { + int ret = VK_SUCCESS; + + for (int layer = 0; layer < layers; ++layer) { + // Start mapping the destination image's memory + StartMemoryMap(stack, layer); + CVKAssertNotNull(pWriteMemory); + + // Get layout of the image (including row pitch) + VkImageSubresource subResource = VkImageSubresource.callocStack(stack); + subResource.set(VK_IMAGE_ASPECT_COLOR_BIT, 0, layer); + VkSubresourceLayout subResourceLayout = VkSubresourceLayout.callocStack(stack); + vkGetImageSubresourceLayout(CVKDevice.GetVkDevice(), GetImageHandle(), subResource, subResourceLayout); + + //////////////////////////////////////////////////////////////////////// + // The image is in the format VK_FORMAT_B8G8R8A8_UNORM: + // CmdBlitImage() automatically converts between formats for us. + // VK_FORMAT_B8G8R8A8_UNORM + // specifies a four-component, 32-bit unsigned normalized format + // that has an 8-bit B component in byte 0, an 8-bit G component + // in byte 1, an 8-bit R component in byte 2, and an 8-bit A component in byte 3. + // The PNG image format is RGB (24-bit) + //////////////////////////////////////////////////////////////////////// + + // Size (in bytes) of the destination image including any padding + final long sourceImageSize = subResourceLayout.size(); + int fileWidth = width; + int fileHeight = height; + int fileSize = (int)sourceImageSize; + if (!needsSwizzle && !writeRaw) { + // Calculate the file width including the padding (in bytes) + fileWidth = (int)(subResourceLayout.rowPitch() / 4); + CVKAssert(fileWidth != 0); + // Calculate the file height (in bytes) + fileHeight = (int)(sourceImageSize / fileWidth) / 4; + } + + try { + byte[] rgb = new byte[fileSize]; + int iDst = 0; + int iSrc = 0; + + ByteBuffer sourceBuffer = pWriteMemory.getByteBuffer((int)sourceImageSize); + + if (fileWidth == width) { + sourceBuffer.get(rgb); + } else { + for (int y = 0; y < fileHeight; ++y) + { + for (int x = 0; x < fileWidth; ++x) + { + // Skip the padded bytes + if (x >= width) { + iSrc += 4; + continue; + } + + rgb[iDst++] = sourceBuffer.get(iSrc++); // red + rgb[iDst++] = sourceBuffer.get(iSrc++); // green + rgb[iDst++] = sourceBuffer.get(iSrc++); // blue + rgb[iDst++] = sourceBuffer.get(iSrc++); // alpha + } + } + } + + DataBuffer buffer = new DataBufferByte(rgb, rgb.length); + + int[] colorFormat = {0,1,2,3}; + if (needsSwizzle && !writeRaw) { + //ABGR->RGBA + colorFormat[0] = 3; + colorFormat[1] = 2; + colorFormat[2] = 1; + colorFormat[3] = 0; + } + + // 4 bytes per pixel: red, green, blue, alpha + WritableRaster raster = Raster.createInterleavedRaster(buffer, + width, height, // width, height of image + 4 * width, // number of bytes per row (stride) + 4, // number of channels (rgb) + colorFormat, // colour format order (iDst.e. byte 0 is first, byte 1 is second ... + // can change order if you want a different format rgb -> bgr + (Point)null); // Upper left point of Raster (not used here) + ColorModel colorModel = new ComponentColorModel(ColorModel.getRGBdefault().getColorSpace(), + true, // keep alpha + false, // is alpha premultiplied + Transparency.TRANSLUCENT, + DataBuffer.TYPE_BYTE); + BufferedImage image = new BufferedImage(colorModel, raster, true, null); + bufferedImages.add(image); + } finally { + EndMemoryMap(); + } + } + + return ret; + } + + + /** + * Creates a file from filename and saves this image to it + * + * @param filename + * @return errorCode + */ + public int SaveToFile(String filename) { + File file = new File(filename); + return SaveToFile(file); + } + + /** + * Saves this image to file (in PNG format) + * @param file + * @return + */ + public int SaveToFile(File file) { + CVKAssertNotNull(file); + int ret; + + try( MemoryStack stack = stackPush()) { + // Copy this image into CPU memory + AtomicReference destImage = new AtomicReference<>(null); + ret = CopyToCPUMemoryImage(stack, destImage); + if (VkFailed(ret)) { return ret; } + CVKAssertNotNull(destImage.get()); + + // Save to the given file + if (destImage.get() != null) { + ret = destImage.get().SaveImageToFile(stack, file); + if (VkFailed(ret)) { return ret; } + + // Cleanup + destImage.get().Destroy(); + destImage.set(null); + } + + } + return ret; + } + + public int SaveToBufferedImages(List layers) { + CVKAssertNotNull(layers); + int ret; + + try( MemoryStack stack = stackPush()) { + // Copy this image into CPU memory + AtomicReference destImage = new AtomicReference<>(null); + ret = CopyToCPUMemoryImage(stack, destImage); + if (VkFailed(ret)) { return ret; } + CVKAssertNotNull(destImage.get()); + + // Save to the given file + if (destImage.get() != null) { + ret = destImage.get().SaveImageToMemory(stack, layers, true); + if (VkFailed(ret)) { return ret; } + + // Cleanup + destImage.get().Destroy(); + destImage.set(null); + } + + } + return ret; + } + + public int ReadPixel(int x, int y) { + int pixel = 0; + + if (IsValid()) { + try (MemoryStack stack = stackPush()) { + int ret = StartMemoryMap(stack, 0); + checkVKret(ret); + + // It's possible the mapping will fail as both the event and render + // thread access the pick buffer for hit testing. Missing a frame + // of hit testing won't cause any issues so let it slide. + if (pWriteMemory != null) { + pixel = ReadPixel(stack, x, y); + EndMemoryMap(); + } + } + } + + return pixel; + } + + private int ReadPixel(MemoryStack stack, int x, int y) { + CVKAssertNotNull(pWriteMemory); + + int pixel = 0; + + // Get layout of the image + VkImageSubresource subResource = VkImageSubresource.callocStack(stack); + subResource.set(VK_IMAGE_ASPECT_COLOR_BIT, 0, 0); + VkSubresourceLayout subResourceLayout = VkSubresourceLayout.callocStack(stack); + vkGetImageSubresourceLayout(CVKDevice.GetVkDevice(), GetImageHandle(), subResource, subResourceLayout); + + // VK_FORMAT_R32_SFLOAT + // Size (in bytes) of the destination image including any padding + final long sourceImageSize = subResourceLayout.size(); + // Calculate the file width including the padding (in bytes) + int fileWidth = (int)(subResourceLayout.rowPitch() / 4); + + try { + FloatBuffer sourceBuffer = pWriteMemory.getFloatBuffer((int)sourceImageSize); + pixel = (int)sourceBuffer.get(x + (y * fileWidth)); + + } + catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + GetLogger().LogException(e, "Get ID from Image Memory failed"); + return CVK_ERROR_IMAGE_GET_ID_FAILED; + } + + return pixel; + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKSwapChainImage.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKSwapChainImage.java new file mode 100644 index 0000000000..c036c98998 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/resourcetypes/CVKSwapChainImage.java @@ -0,0 +1,60 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package au.gov.asd.tac.constellation.visual.vulkan.resourcetypes; + +import static org.lwjgl.vulkan.KHRSwapchain.VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_ASPECT_COLOR_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_TYPE_2D; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; +import static org.lwjgl.vulkan.VK10.VK_IMAGE_VIEW_TYPE_2D; +import static org.lwjgl.vulkan.VK10.VK_NULL_HANDLE; + +/** + * SwapChainImage + * + */ +public class CVKSwapChainImage extends CVKImage { + private CVKSwapChainImage() {} + + public static CVKSwapChainImage Create(long imageHandle, + int format, + String debugName) { + CVKSwapChainImage cvkImage = new CVKSwapChainImage(); + cvkImage.DEBUGNAME = debugName; + cvkImage.width = 0; + cvkImage.height = 0; + cvkImage.layers = 1; + cvkImage.format = format; + cvkImage.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + cvkImage.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + cvkImage.imageType = VK_IMAGE_TYPE_2D; + cvkImage.viewType = VK_IMAGE_VIEW_TYPE_2D; + cvkImage.layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + cvkImage.pImage.put(0, imageHandle); + + cvkImage.CreateImageView(null); + + return cvkImage; + } + + + @Override + public void Destroy() { + // Don't call super! + // Don't destroy the image handles as the swapchain objects owns their memory + DestroyImageView(); + ResetImageHandle(); + } + + private void ResetImageHandle() { + pImage.put(0, VK_NULL_HANDLE); + } + + public void SetExtent(int width, int height) { + this.width = width; + this.height = height; + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Blaze.fs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Blaze.fs new file mode 100644 index 0000000000..0536ce6894 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Blaze.fs @@ -0,0 +1,32 @@ +#version 450 + + +// === CONSTANTS === +const float ICON_BORDER = 0.125; + + +// === UNIFORMS === +layout(std140, binding = 3) uniform UniformBlock { + float opacity; +} ub; + + +// === PER FRAGMENT DATA IN === +layout(location = 0) flat in vec4 fColor; +layout(location = 1) in vec2 pointCoord; +layout(location = 2) in float fnradius; +layout(location = 3) flat in int isPointer; + + +// === PER FRAGMENT DATA OUT === +layout(location = 0) out vec4 fragColor; + + +void main() { + + // Blazes don't do icons for now. + // Comment out the rest of the code to avoid "fData not used" errors when linking on some drivers. + if (isPointer != 0) { + fragColor = vec4(fColor.rgb * (1.0 - pointCoord.x * pointCoord.x), ub.opacity); + } +} \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Blaze.gs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Blaze.gs new file mode 100644 index 0000000000..a30b9fcf2d --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Blaze.gs @@ -0,0 +1,80 @@ +#version 450 + + +// === CONSTANTS === +const float PI = 3.14159265; +const float border = 0.002; + + +// === UNIFORMS === +layout(std140, binding = 2) uniform UniformBlock { + mat4 pMatrix; + float scale; + float visibilityLow; + float visibilityHigh; +} ub; + + +// === PER PRIMITIVE DATA IN === +layout(points) in; +layout(location = 0) flat in vec4 gColor[]; +layout(location = 1) flat in ivec4 gData[]; +layout(location = 2) in float gnradius[]; + + +// === PER PRIMITIVE DATA OUT === +layout(triangle_strip, max_vertices=3) out; +layout(location = 0) flat out vec4 fColor; +layout(location = 1) out vec2 pointCoord; +layout(location = 2) out float fnradius; +layout(location = 3) flat out int isPointer; //<- remove this + + +vec4 rotate(vec4 v, float dx, float dy, float x, float y) { + return ub.pMatrix * vec4(v.x + y * dx - x * dy, v.y + x * dx + y * dy, v.z, v.w); +} + + +void main() { + float visibility = gColor[0][3]; + if(visibility > max(ub.visibilityLow, 0) && (visibility <= ub.visibilityHigh || visibility > 1.0)) { + vec4 v = gl_in[0].gl_Position; + float angle = radians(float(gData[0].p)); + + // The GL angle has 0 pointing east incrementing anti-clockwise, just like real maths. + // We want 0 pointing north incrementing clockwise. + angle = PI/2-angle; + + float dx = cos(angle); + float dy = sin(angle); + + v.x += gnradius[0] * dx; + v.y += gnradius[0] * dy; + + dx *= ub.scale * 0.5 * v.z/-10; + dy *= ub.scale * 0.5 * v.z/-10; + + isPointer = 1; + fColor = gColor[0]; + pointCoord = vec2(0.4, 0); + gl_Position = rotate(v, dx, dy, 0, 0); + gl_Position.y = -gl_Position.y; + EmitVertex(); + + isPointer = 1; + fColor = gColor[0]; + pointCoord = vec2(-0.7, 1); + gl_Position = rotate(v, dx, dy, -1, 5); + gl_Position.y = -gl_Position.y; + EmitVertex(); + + isPointer = 1; + fColor = gColor[0]; + pointCoord = vec2(0.7, 1); + gl_Position = rotate(v, dx, dy, 1, 5); + gl_Position.y = -gl_Position.y; + EmitVertex(); + + EndPrimitive(); + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Blaze.vs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Blaze.vs new file mode 100644 index 0000000000..85f597a371 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Blaze.vs @@ -0,0 +1,46 @@ +// Draw blazes next to nodes. +#version 450 + + +// === PUSH CONSTANT === +layout(std140, push_constant) uniform PushConstant { + mat4 mvMatrix; +} pc; + + +// === UNIFORMS === +layout(std140, binding = 0) uniform UniformBlock { + float morphMix; +} ub; +layout(binding = 1) uniform samplerBuffer xyzTexture; + + +// === PER VERTEX DATA IN === +layout(location = 0) in vec4 blazeColor; +layout(location = 1) in ivec4 blazeData; + + +// === PER VERTEX DATA OUT === +layout(location = 0) flat out vec4 gColor; +layout(location = 1) flat out ivec4 gData; +layout(location = 2) out float gnradius; + + +void main(void) { + // Pass stuff to the next shader. + gColor = blazeColor; + gData = blazeData; + + // Find the xyz of the vertex that this blaze belongs to, + // specified by an offset into the xyzTexture buffer. + int offset = blazeData[0] * 2; + vec3 v = texelFetch(xyzTexture, offset).stp; + vec3 vEnd = texelFetch(xyzTexture, offset + 1).stp; + vec3 mixedVertex = mix(v, vEnd, ub.morphMix); + + gl_Position = pc.mvMatrix * vec4(mixedVertex, 1); + + // Get the side radius of the associated vertex and pass that through + // so the blaze is drawn relative to the node size. + gnradius = texelFetch(xyzTexture, offset).q; +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/CVKShaderPlaceHolder.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/CVKShaderPlaceHolder.java new file mode 100644 index 0000000000..6fca89a451 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/CVKShaderPlaceHolder.java @@ -0,0 +1,20 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.shaders; + +public class CVKShaderPlaceHolder { + +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/ConnectionLabel.vs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/ConnectionLabel.vs new file mode 100644 index 0000000000..c836e19331 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/ConnectionLabel.vs @@ -0,0 +1,389 @@ +// Draw labels for connections. +// The labels are drawn glyph by glyph: each shader call draws one glyph. +#version 450 + + +// === CONSTANTS === +// The z distance from the camera at which a label is no longer visible +const float LABEL_VISIBLE_DISTANCE = 150; + +// Label scales are sent to the shader as integers between 1 and 64. Dividing by this factor converts them +// to float values between 0 and 4 as a proportion of the radius of the node. +const float LABEL_TO_NRADIUS_UNITS = 1 / 16.0; + +// Line widths are sent to the shader as integers where 256 is equivalent to a node radius of one +const float LINE_WIDTH_TO_NRADIUS_UNITS = 1 / 256.0; + +// The amount to darken the background of the labels with respect to the background of the graph. +// 1 means no darkening, 0 means completely black +const float BACKGROUND_DARKENING_FACTOR = 0.8; + +// A constant that is used to scale all labels consistently that was chosen long ago for aesthetic reasons +// and hence needs to remain to ensure consistency across graphs until a new version of the schema addressing this issue is released. +const float LABEL_AESTHETIC_SCALE = 5 / 8.0; + +// An approximation of the trigonometric tangent function evaluated at various degree values. +const float SIN_EIGHTY_DEGREES = 0.98481; + +// A small constant added to the depths of labels in cases where they would otherwise appear past both nodes of their conncetion. +const float LABEL_DEPTH_PUSHBACK = 0.001; + +// The GL version this shader started as used the shader implicit gl_DepthRange which doesn't exist for Vulkan, luckily we never +// set it via glDepthRange(float, float); so it was always at the default projection Z range (aka depth) of -1..1. Note in Vulkan +// depth is mapped 0..1. We've used the GL default as that is what the shader was written to expect but keep an eye for issues. +const float DEPTH_NEAR = -1.0; + + +// === PUSH CONSTANTS === +layout(std140, push_constant) uniform ModelViewPushConstant { + // Matrix to project from world coordinates to camera coordinates + mat4 mvMatrix; + + // This push constant allows connection labels to draw summary and attribute + // labels with different vertex buffers but without requiring dynamic uniform + // buffers. The switch determines which label info matrix in the UBO below + // to use. + // 1 for attribute label, 0 for summary label + int isAttributeLabel; +} pc; + +// === UNIFORMS === + +layout(std140, binding = 0) uniform UniformBlock { + // Each column is a connection label with the following structure: + // [0..2] rgb colour (note label colours do not habve an alpha) + // [3] label size + mat4 attributeLabelInfo; + mat4 summaryLabelInfo; + + // Information from the graph's visual state + float morphMix; + float visibilityLow; + float visibilityHigh; + + // The index of the background glyph in the glyphInfo texture + int backgroundGlyphIndex; + + // Used to draw the label background. + vec4 backgroundColor; +} ub; + +// .xyz = world coordinates of node. +// .a = radius of node. +layout(binding = 1) uniform samplerBuffer xyzTexture; + + +// === PER VERTEX DATA IN === + +// [0] label width +// [1..2] x and y offsets of this glyph from the top centre of the line of text +// [3] The visibility of this glyph (constant for a node, but easier to pass in the batch). +layout(location = 0) in vec4 glyphLocationData; + +// [0] The index of the low node containing this glyph in the xyzTexture +// [1] The index of the high node containing this glyph in the xyzTexture +// [2] Packed value: The label number in which this glyph occurs, total scale and offset +// [3] Packed value: Glyph index and stagger +layout(location = 1) in ivec4 graphLocationData; + + +// === PER VERTEX DATA OUT === +// TODO: squash these + +// Information about the texture location, colour and scale of the glyph +layout(location = 0) out int glyphIndex; +layout(location = 1) out vec4 labelColor; +layout(location = 2) out float glyphScale; +// The scaling factor if the glyph we are rendering is in fact the background for a line of text. +// This will be one in all other cases. +layout(location = 3) out float backgroundScalingFactor; +// A value describing the side of the background on which an indicator should be for connection labels. +// This will be zero when no indicator should be drawn. +layout(location = 4) out int drawIndicator; +// Locations for placing the aforementioned indicator. +layout(location = 5) out float drawIndicatorX; +layout(location = 6) out float drawIndicatorY; +// The depth of the label which is used to bring labels in front of connections. +layout(location = 7) out float depth; + + +void main(void) { + + float labelWidth = glyphLocationData[0]; + float glyphXOffset = glyphLocationData[1]; + float glyphYOffset = glyphLocationData[2]; + float glyphVis = glyphLocationData[3]; + + int lowNodeIndex = graphLocationData[0]; + int highNodeIndex = graphLocationData[1]; + float offset = LINE_WIDTH_TO_NRADIUS_UNITS * (graphLocationData[2] >> 16); + int totalScale = (graphLocationData[2] >> 2) & 0x3FF; + int labelNumber = graphLocationData[2] & 0x3; + glyphIndex = (graphLocationData[3] >> 8) & 0xFFF; + float stagger = (graphLocationData[3] & 0xFF) / 256.0; + + // Find the xyz of the endpoint vertices, + // specified by an offset into the xyzTexture buffer. + int lowOffset = lowNodeIndex * 2; + vec4 v1 = texelFetch(xyzTexture, lowOffset); + vec4 v1End = texelFetch(xyzTexture, lowOffset + 1); + vec4 mixedV1 = mix(v1, v1End, ub.morphMix); + + int highOffset = highNodeIndex * 2; + vec4 v2 = texelFetch(xyzTexture, highOffset); + vec4 v2End = texelFetch(xyzTexture, highOffset + 1); + vec4 mixedV2 = mix(v2, v2End, ub.morphMix); + + // Calculate the unit vector parallel to the connection + v1 = pc.mvMatrix * vec4(mixedV1.xyz, 1); + v2 = pc.mvMatrix * vec4(mixedV2.xyz, 1); + vec3 connectionDirection = normalize(v1.xyz - v2.xyz); + + // The unit vector perpendicular to the connection in the x-y plane + vec3 connectionPerpendicular = normalize(cross(vec3(connectionDirection.xy, 0), vec3(0, 0, 1))); + + // Offset both nodes to match the offset of the connection being labeled + // This offset occurs when more than one parallel edge/transaction is drawn between two nodes. + v1.xy += connectionPerpendicular.xy * offset; + v2.xy += connectionPerpendicular.xy * offset; + + // Calculate the pixel coordinates of the correct place along the connection + if (length(v1.xyz - v2.xyz) - mixedV1.w - mixedV2.w > 0) { + v1.xyz -= mixedV1.w * connectionDirection; + v2.xyz += mixedV2.w * connectionDirection; + } + vec4 connectionLocation = vec4(mix(v1.xyz, v2.xyz, stagger), 1); + + // Get the size and colour of this label from the relevant label information matrix + if (pc.isAttributeLabel == 1) { + glyphScale = ub.attributeLabelInfo[labelNumber][3] * LABEL_TO_NRADIUS_UNITS * LABEL_AESTHETIC_SCALE; + } else { + glyphScale = ub.summaryLabelInfo[labelNumber][3] * LABEL_TO_NRADIUS_UNITS * LABEL_AESTHETIC_SCALE; + } + + // Determine visiblity of this label based both on the visibility of the associated node, and the fade out distance for labels. + float distance = -connectionLocation.z; + float alpha = (glyphVis > max(ub.visibilityLow, 0) && (glyphVis <= ub.visibilityHigh || glyphVis > 1.0)) ? + 1 - smoothstep((LABEL_VISIBLE_DISTANCE-20) * glyphScale, LABEL_VISIBLE_DISTANCE * glyphScale, distance) : 0.0; + + // The total vertical offset of the label from the line joining two nodes. + float labelYOffset = (totalScale * LABEL_TO_NRADIUS_UNITS * LABEL_AESTHETIC_SCALE); + // We need to subtract half the size of the first connection label from every line's Y offset, + // since we want the connection to align with the first label's centre (rather than its top). + if (pc.isAttributeLabel == 1) { + labelYOffset -= 0.5 * ub.attributeLabelInfo[0][3] * LABEL_TO_NRADIUS_UNITS * LABEL_AESTHETIC_SCALE; + } else { + labelYOffset -= 0.5 * ub.summaryLabelInfo[0][3] * LABEL_TO_NRADIUS_UNITS * LABEL_AESTHETIC_SCALE; + } + + // Set the colour appropritely - this comes from the ub.labelInfo matrix for a normal glyph, or the graph background colour + // if it is a background glyph. + if (pc.isAttributeLabel == 1) { + labelColor = glyphIndex == ub.backgroundGlyphIndex ? + vec4(ub.backgroundColor.xyz * BACKGROUND_DARKENING_FACTOR, alpha) : + vec4(ub.attributeLabelInfo[labelNumber].xyz, alpha); + } else { + labelColor = glyphIndex == ub.backgroundGlyphIndex ? + vec4(ub.backgroundColor.xyz * BACKGROUND_DARKENING_FACTOR, alpha) : + vec4(ub.summaryLabelInfo[labelNumber].xyz, alpha); + } + + // Calculate the pixel coordinates of the glyph's location on the graph + vec4 locationOffset = vec4(glyphXOffset * glyphScale, -(glyphYOffset * glyphScale) - labelYOffset, 0, 0); + vec4 labelLocation = connectionLocation + vec4(labelWidth * glyphScale, -labelYOffset, 0, 0); + + // In this section of the shader we calculate where the connection intecepts the boundary of the label + // We use this for two purposes: + // - to set the depth of the whole label to the closest z-position of all the intercepts thereby bringing it in front of the connection + // - to draw indicators on the label which point at the connection. + float distanceAlongConnection; + float connectionInterceptX; + float connectionInterceptY; + float topIntercept; + float topInterceptZ; + bool hasTop = false; + float bottomIntercept; + float bottomInterceptZ; + bool hasBottom = false; + float leftIntercept; + float leftInterceptZ; + bool hasLeft = false; + float rightIntercept; + float rightInterceptZ; + bool hasRight = false; + depth = labelLocation.z; + drawIndicator = 0; + + // The scaling factor for the background glyph - used to draw the background at the correct size in the geometry shader + backgroundScalingFactor = abs(2 * labelWidth); + // The location of the bottom right corner of the label + vec4 labelBRLocation = labelLocation + vec4(glyphScale * backgroundScalingFactor, -glyphScale, 0, 0); + + // Look for an intercept at the top of the label + float beta = labelLocation.y / labelLocation.z; + if (beta * connectionDirection.z - connectionDirection.y != 0) { + // Find the distance from v1 along the connection to the point where the ray from the camera through the + // top of the label meets the connection. + distanceAlongConnection = (v1.y - beta * v1.z) / (beta * connectionDirection.z - connectionDirection.y); + // Find the coordinates of the aforementioned point + connectionInterceptX = v1.x + distanceAlongConnection * connectionDirection.x; + topInterceptZ = v1.z + distanceAlongConnection * connectionDirection.z; + // Check that this point actually lies between v1 and v2 + if (-topInterceptZ > DEPTH_NEAR && connectionInterceptX >= min(v1.x, v2.x) && connectionInterceptX <= max(v1.x, v2.x)) { + // Calculate the intercept on the top of the label by scaling this point + topIntercept = (connectionInterceptX * labelLocation.z) / topInterceptZ; + // If the intercept actually lies on the top side of the label, flag that there is a top intercept + if (topIntercept > labelLocation.x && topIntercept < labelLocation.x + glyphScale * backgroundScalingFactor) { + hasTop = true; + depth = labelLocation.z + abs(topInterceptZ - labelLocation.z); + } + } + } + // Look for an intercept at the bottom of the label + beta = labelBRLocation.y / labelBRLocation.z; + if (beta*connectionDirection.z - connectionDirection.y != 0) { + // Find the distance from v1 along the connection to the point where the ray from the camera through the + // bottom of the label meets the connection. + distanceAlongConnection = (v1.y - beta * v1.z) / (beta*connectionDirection.z - connectionDirection.y); + // Find the coordinates of the aforementioned point + connectionInterceptX = v1.x + distanceAlongConnection * connectionDirection.x; + bottomInterceptZ = v1.z + distanceAlongConnection * connectionDirection.z; + // Check that this point actually lies between v1 and v2 + if (-bottomInterceptZ > DEPTH_NEAR && connectionInterceptX >= min(v1.x, v2.x) && connectionInterceptX <= max(v1.x, v2.x)) { + // Calculate the intercept on the bottom of the label by scaling this point + bottomIntercept = (connectionInterceptX * labelBRLocation.z) / bottomInterceptZ; + // If the intercept actually lies on the bottom side of the label, flag that there is a bottom intercept + if (bottomIntercept > labelLocation.x && bottomIntercept < labelLocation.x + glyphScale * backgroundScalingFactor) { + hasBottom = true; + } + } + } + + // If either a top or bottom intercept was found, set the depth and details about the label indicator appropriately. + if (hasTop && hasBottom) { + if (topInterceptZ > bottomInterceptZ) { + drawIndicatorX = topIntercept; + drawIndicatorY = 0; + depth = topInterceptZ; + drawIndicator = 1; + } else { + drawIndicatorX = bottomIntercept; + drawIndicatorY = -glyphScale; + depth = bottomInterceptZ; + drawIndicator = 2; + } + } else if (hasTop) { + drawIndicatorX = topIntercept; + drawIndicatorY = 0; + depth = max(topInterceptZ, bottomInterceptZ); + drawIndicator = 1; + } else if (hasBottom) { + drawIndicatorX = bottomIntercept; + drawIndicatorY = -glyphScale; + depth = max(topInterceptZ, bottomInterceptZ); + drawIndicator = 2; + } + + // If there are no top or bottom intercepts, look for one on the left or the right + else { + // Look for an intercept at the left of the label + beta = labelLocation.x / labelLocation.z; + if (beta * connectionDirection.z - connectionDirection.x != 0) { + // Find the distance from v1 along the connection to the point where the ray from the camera through the + // left of the label meets the connection. + distanceAlongConnection = (v1.x - beta * v1.z) / (beta * connectionDirection.z - connectionDirection.x); + // Find the coordinates of the aforementioned point + connectionInterceptY = v1.y + distanceAlongConnection * connectionDirection.y; + leftInterceptZ = v1.z + distanceAlongConnection*connectionDirection.z; + // Check that this point actually lies between v1 and v2 + if (-leftInterceptZ > DEPTH_NEAR && connectionInterceptY >= min(v1.y, v2.y) && connectionInterceptY <= max(v1.y, v2.y)) { + // Calculate the intercept on the left of the label by scaling this point + leftIntercept = (connectionInterceptY * labelLocation.z) / leftInterceptZ; + // If the intercept actually lies on left top side of the label, flag that there is a top intercept + if (leftIntercept < labelLocation.y && leftIntercept > labelLocation.y - glyphScale) { + hasLeft = true; + } + } + } + // Look for an intercept at the right of the label + beta = labelBRLocation.x / labelBRLocation.z; + if (beta*connectionDirection.z - connectionDirection.x != 0) { + // Find the distance from v1 along the connection to the point where the ray from the camera through the + // right of the label meets the connection. + distanceAlongConnection = (v1.x - beta*v1.z) / (beta * connectionDirection.z - connectionDirection.x); + // Find the coordinates of the aforementioned point + connectionInterceptY = v1.y + distanceAlongConnection * connectionDirection.y; + rightInterceptZ = v1.z + distanceAlongConnection * connectionDirection.z; + // Check that this point actually lies between v1 and v2 + if (-rightInterceptZ > DEPTH_NEAR && connectionInterceptY >= min(v1.y, v2.y) && connectionInterceptY <= max(v1.y, v2.y)) { + // Calculate the intercept on the right of the label by scaling this point + rightIntercept = (connectionInterceptY * labelBRLocation.z) / rightInterceptZ; + // If the intercept actually lies on the right side of the label, flag that there is a top intercept + if (rightIntercept < labelLocation.y && rightIntercept > labelLocation.y - glyphScale) { + hasRight = true; + } + } + } + + // If either a left or right intercept was found, set the depth and details about the label indicator appropriately. + if (hasLeft && hasRight) { + if (leftInterceptZ > rightInterceptZ) { + drawIndicatorX = 0; + drawIndicatorY = leftIntercept; + drawIndicator = 3; + depth = leftInterceptZ; + } else { + drawIndicatorX = glyphScale*backgroundScalingFactor; + drawIndicatorY = rightIntercept; + drawIndicator = 4; + depth = rightInterceptZ; + } + } else if (hasLeft) { + drawIndicatorX = 0; + drawIndicatorY = leftIntercept; + depth = max(leftInterceptZ, rightInterceptZ); + drawIndicator = 3; + } else if (hasRight) { + drawIndicatorX = glyphScale * backgroundScalingFactor; + drawIndicatorY = rightIntercept; + depth = max(leftInterceptZ, rightInterceptZ); + drawIndicator = 4; + } + + } + + // If we are not looking at the background glyph and the first label, we don't want to draw an indicator. + if (glyphIndex != ub.backgroundGlyphIndex || totalScale != 0) { + drawIndicator = 0; + } + // If we are not looking at the background glyph, reset the background scaling factor to the default + if (glyphIndex != ub.backgroundGlyphIndex) { + backgroundScalingFactor = 1; + } + + // Used to brings glyphs slightly forward - text glyphs are brought further forward than the background glyph. + // Note that both of these quantities are strictly smaller than LABEL_DEPTH_PUSHBACK + float bringForward = glyphIndex == ub.backgroundGlyphIndex ? 0.00025 : 0.00075; + + + // If the calculated depth is in front of the connection's anterior node, + // clamp it to the depth of this node push it backwards a small amount. + if (depth >= max(v1.z, v2.z)) { + depth = max(v1.z, v2.z) - LABEL_DEPTH_PUSHBACK; + } + // If the calculated depth is behind the connection's posterior node, + // clamp it to the depth of this node and push it forward a small amount. + if (depth <= min(v1.z, v2.z)) { + depth = min(v1.z, v2.z) + LABEL_DEPTH_PUSHBACK; + } + // If the calculated depth is in front of the near plane, + // clamp it to the depth of this plane and push it backwards a small amount. + if(-depth < DEPTH_NEAR) { + depth = -DEPTH_NEAR - LABEL_DEPTH_PUSHBACK; + } + // Bring this glyph forwards by the appropriate amount depending on whether or not it is the background glyph. + depth += bringForward; + + // Output the location of this glyph. + gl_Position = connectionLocation + locationOffset; +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Label.fs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Label.fs new file mode 100644 index 0000000000..fdf40aab96 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Label.fs @@ -0,0 +1,43 @@ +#version 450 + + +// === CONSTANTS === +// The transperancy threshold before we stop drawing glyphs +// Prevents some of the artifacts we get from drawing things behind barely visible pixels that exist to anti-alias the glyph. +const float ALPHA_THRESHOLD = 0.1; + + +// === UNIFORMS === +// Texture containing the grayscale images of the glyphs. +// .r = transperancy at a given pixel location of the glyph +// .gba = unused +layout(binding = 4) uniform sampler2DArray glyphImageTexture; + + +// === PER FRAGMENT DATA IN === +layout(location = 0) noperspective centroid in vec3 textureCoordinates; +layout(location = 1) in vec4 fLabelColor; +layout(location = 2) flat in float fDepth; + + +// === PER FRAGMENT DATA OUT === +layout(location = 0) out vec4 fragColor; + + +void main(void) { + + // Lookup the texture + vec4 color = texture(glyphImageTexture, textureCoordinates); + + // Colour to emmit is that passes in with its alpha multiplied by the value from the glyph image texture + // If the resulting alpha is below the threshold we discard. + float alpha = color.r * fLabelColor.a; + if (alpha < ALPHA_THRESHOLD) { + discard; + } + fragColor = vec4(fLabelColor.rgb, alpha); + + // Set the depth as specified by the geometry shader + gl_FragDepth = fDepth; + +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Label.gs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Label.gs new file mode 100644 index 0000000000..6c48164a38 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Label.gs @@ -0,0 +1,181 @@ +// Geometry shader for labels. +// Outputs the 4 corners of a glyph, and if we are drawing the background of a connection label, possibly the 3 corners +// of an indicator triangle pointing at the conenction. +#version 450 + + +// === CONSTANTS === +// If the point is beyond this distance, don't draw it. +// Keep this in sync with the text fadeout in the fragment shader. +const float VISIBLE_DISTANCE = 150; + +// The size of the indicator triangle on connection labels. +const float INDICATOR_SIZE = 0.1; + +// An approximation of the trigonometric tangent function evaluated at various degree values. +const float SIN_SIXTY_DEGREES = 0.86603; + +// The GL version this shader started as used the shader implicit gl_DepthRange which doesn't exist for Vulkan, luckily we never +// set it via glDepthRange(float, float); so it was always at the default projection Z range (aka depth) of -1..1. Note in Vulkan +// depth is mapped 0..1. We've used the GL default as that is what the shader was written to expect but keep an eye for issues. +const float DEPTH_NEAR = -1.0; +const float DEPTH_FAR = 1.0; + + +// === UNIFORMS === +layout(std140, binding = 2) uniform UniformBlock { + // Matrix to convert from camera coordinates to scene coordinates. + mat4 pMatrix; + + // The scaling factor to convert from texture coordinates to world unit coordinates + float widthScalingFactor; + float heightScalingFactor; + + // Used to draw the connection indicator on the label background. + vec4 highlightColor; +} ub; + +// [0..1] x,y coordinates of glyph in glyphImageTexture. The whole part of x is the texture number +// [2..3] width, height of glyph in glyphImageTexture +layout(binding = 3) uniform samplerBuffer glyphInfoTexture; + + +// === PER PRIMITIVE DATA IN === +layout(points) in; + +// The scaling factor if the glyph we are rendering is in fact the background for a line of text. +// This will be one in all other cases. +layout(location = 0) in int glyphIndex[]; +layout(location = 1) in vec4 labelColor[]; +layout(location = 2) in float glyphScale[]; +layout(location = 3) in float backgroundScalingFactor[]; +layout(location = 4) in int drawIndicator[]; +layout(location = 5) in float drawIndicatorX[]; +layout(location = 6) in float drawIndicatorY[]; +layout(location = 7) in float depth[]; + + +// === PER PRIMITIVE DATA OUT === +layout(triangle_strip, max_vertices=7) out; + +// The coordinates to lookup the glyph in the glyphImageTexture +layout(location = 0) noperspective centroid out vec3 textureCoordinates; +// The colour of this glyph (constant for a whole label, unless we are rendering its background or connection indicator). +layout(location = 1) out vec4 fLabelColor; +layout(location = 2) flat out float fDepth; + + +void main(void) { + + if(labelColor[0].a > 0) { + // Retrive the positional information about the glyph from the glypInfoTexture + vec4 glyphInfo = texelFetch(glyphInfoTexture, glyphIndex[0]); + int glyphPage = int(glyphInfo[0]); + float glyphX = glyphInfo[0] - glyphPage; + float glyphY = glyphInfo[1]; + float glyphWidth = glyphInfo[2]; + float glyphHeight = glyphInfo[3]; + + // The upper-left of the glyph in camera coordinates, as calculated from the vertex shader + vec4 glyphLocation = gl_in[0].gl_Position; + + // Calculate depth + vec4 depthVec = ub.pMatrix * vec4(glyphLocation.xy, depth[0], glyphLocation.w); + float calcdDepth = ((DEPTH_FAR - DEPTH_NEAR) * (depthVec.z / depthVec.w) + DEPTH_FAR + DEPTH_NEAR) / 2.0; + + // The dimensions (in camera coordinates) of the glyph + float width = glyphWidth * glyphScale[0] * ub.widthScalingFactor * backgroundScalingFactor[0]; + float height = glyphHeight * glyphScale[0] * ub.heightScalingFactor; + + // Emitt the four corners of the glyph in screen coordinates. + // Upper Left + fLabelColor = labelColor[0]; + textureCoordinates = vec3(glyphX, glyphY, glyphPage); + fDepth = calcdDepth; + gl_Position = ub.pMatrix * glyphLocation; + gl_Position.y = -gl_Position.y; + EmitVertex(); + + // Lower Left + fLabelColor = labelColor[0]; + textureCoordinates = vec3(glyphX, glyphY + glyphHeight, glyphPage); + fDepth = calcdDepth; + vec4 locationOffset = vec4(0, -height, 0, 0); + gl_Position = ub.pMatrix * (glyphLocation + locationOffset); + gl_Position.y = -gl_Position.y; + EmitVertex(); + + // Upper Right + fLabelColor = labelColor[0]; + textureCoordinates = vec3(glyphX + glyphWidth, glyphY, glyphPage); + fDepth = calcdDepth; + locationOffset = vec4(width, 0, 0, 0); + gl_Position = ub.pMatrix * (glyphLocation + locationOffset); + gl_Position.y = -gl_Position.y; + EmitVertex(); + + // Lower Right + fLabelColor = labelColor[0]; + textureCoordinates = vec3(glyphX + glyphWidth, glyphY + glyphHeight, glyphPage); + fDepth = calcdDepth; + locationOffset = vec4(width, -height, 0, 0); + gl_Position = ub.pMatrix * (glyphLocation + locationOffset); + gl_Position.y = -gl_Position.y; + EmitVertex(); + + EndPrimitive(); + + // Draw an indicator for a connection label if necessary. + if (drawIndicator[0] != 0) { + + // recalculate depth for direction indicator noting that we need to push the depth slightly forward of the + // background glyph, but not as far forward as the other glyphs. + float forward = 0.00025; + vec4 depthVec = ub.pMatrix * vec4(glyphLocation.xy, depth[0] + forward, glyphLocation.w); + float calcdDepth = ((DEPTH_FAR - DEPTH_NEAR)*(depthVec.z / depthVec.w) + DEPTH_FAR + DEPTH_NEAR) / 2.0; + + // The indicator colour is the graph highlight colour, with the alpha value that is constant across the label. + vec4 indicatorColor = vec4(ub.highlightColor.xyz, 1.5 * labelColor[0].a); + vec4 trianglePoint, triangleBase1, triangleBase2; + + if (drawIndicator[0] == 1 || drawIndicator[0] == 2) { + // The indicator is an isosecles triangle pointing to the connection intersecting the top or bottom of the label background. + float triangleDrop = drawIndicator[0] == 1 ? -INDICATOR_SIZE*3 : INDICATOR_SIZE*3; + trianglePoint = vec4(drawIndicatorX[0] - glyphLocation.x, drawIndicatorY[0], 0, 0); + triangleBase1 = vec4(drawIndicatorX[0] - glyphLocation.x + INDICATOR_SIZE, drawIndicatorY[0] + triangleDrop, 0, 0); + triangleBase2 = vec4(drawIndicatorX[0] - glyphLocation.x - INDICATOR_SIZE, drawIndicatorY[0] + triangleDrop, 0, 0); + } else if (drawIndicator[0] == 3 || drawIndicator[0] == 4) { + // The indicator is an isosecles triangle pointing to the connection intersecting the left or right of the label background. + float triangleDrop = drawIndicator[0] == 3 ? INDICATOR_SIZE * 3 : -INDICATOR_SIZE * 3; + trianglePoint = vec4(drawIndicatorX[0], drawIndicatorY[0]-glyphLocation.y, 0, 0); + triangleBase1 = vec4(drawIndicatorX[0] + triangleDrop, drawIndicatorY[0]-glyphLocation.y + INDICATOR_SIZE, 0, 0); + triangleBase2 = vec4(drawIndicatorX[0] + triangleDrop, drawIndicatorY[0]-glyphLocation.y - INDICATOR_SIZE, 0, 0); + } + + + // Emit the three vertices for the indicator triangle. + fLabelColor = indicatorColor; + textureCoordinates = vec3(glyphX, glyphY, glyphPage); + fDepth = calcdDepth; + gl_Position = ub.pMatrix * (glyphLocation + trianglePoint); + gl_Position.y = -gl_Position.y; + EmitVertex(); + + fLabelColor = indicatorColor; + textureCoordinates = vec3(glyphX, glyphY, glyphPage); + fDepth = calcdDepth; + gl_Position = ub.pMatrix * (glyphLocation + triangleBase1); + gl_Position.y = -gl_Position.y; + EmitVertex(); + + fLabelColor = indicatorColor; + textureCoordinates = vec3(glyphX, glyphY, glyphPage); + fDepth = calcdDepth; + gl_Position = ub.pMatrix * (glyphLocation + triangleBase2); + gl_Position.y = -gl_Position.y; + EmitVertex(); + + EndPrimitive(); + } + } +} \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Line.fs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Line.fs new file mode 100644 index 0000000000..9dad4a959b --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Line.fs @@ -0,0 +1,77 @@ +#version 450 + + +// === CONSTANTS === +// We want thin black lines on the edges of the edges so we can see individual lines +// instead of a mass of color. +const float EDGE1 = 1.0/16; +const float EDGE2 = 15.0/16; + +const int LINE_STYLE_SOLID = 0; +const int LINE_STYLE_DOTTED = 1; +const int LINE_STYLE_DASHED = 2; +const int LINE_STYLE_DIAMOND = 3; +const float LINE_DOT_SIZE = 0.3; + + +// === PUSH CONSTANTS === +layout(std140, push_constant) uniform HitTestPushConstant { + // If non-zero, use the texture to color the icon. + // Otherwise, use a unique color for hit testing. + // Offset is 64 as the projection matrix (in Line.vs) + // is before it in the pushConstant buffer. + // Note this is also read by Line.gs + layout(offset = 64) int drawHitTest; +} pc; + + +// === PER FRAGMENT DATA IN === +layout(location = 0) in vec4 pointColor; +layout(location = 1) flat in ivec4 fData; +layout(location = 2) in vec2 pointCoord; +layout(location = 3) flat in float lineLength; + + +// === PER FRAGMENT DATA OUT === +layout(location = 0) out vec4 fragColor; + + +void main(void) { + // Line style. + if (fData.q != LINE_STYLE_SOLID && lineLength > 0) { + float lineStyle = fData.q; + float segmentSize = LINE_DOT_SIZE * (lineLength / (0.25 + lineLength)); + + if (lineStyle == LINE_STYLE_DOTTED) { + float seg = mod(pointCoord.y, 2 * segmentSize); + if(seg > (1 * segmentSize) && seg < (2 * segmentSize)) { + discard; + } + } else if (lineStyle == LINE_STYLE_DASHED) { + float seg = mod(pointCoord.y, 3 * segmentSize); + if(seg > (2 * segmentSize) && seg<(3 * segmentSize)) { + discard; + } + } else if (lineStyle == LINE_STYLE_DIAMOND) { + float seg = mod(pointCoord.y, segmentSize) / segmentSize; + if((pointCoord.x < 0 && seg > pointCoord.x + 1) || (pointCoord.x > 0 && seg > (1 - pointCoord.x))) { + discard; + } else if ((pointCoord.x < 0 && seg < (1 - pointCoord.x) - 1) || (pointCoord.x > 0 && seg < pointCoord.x)) { + discard; + } + } + } + + if (pc.drawHitTest == 0) { + // Make the edges of the line darker so the viewer can distinguish between lines. + if (abs(pointCoord.x) > 0.20) { + fragColor = (1.2 - abs(pointCoord.x)) * pointColor; + } else { + fragColor = pointColor; + } + } else { + // The line index is in pointColor[3]. + // Use it to make a unique color less than zero for hit testing. + fragColor = vec4(-(fData[0] + 1), 0.0, 0.0, 1.0); + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Line.gs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Line.gs new file mode 100644 index 0000000000..aa9c0d9371 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Line.gs @@ -0,0 +1,327 @@ +// LINE geometry shader. +// Points that are marked with the hidden indicator (color.a<0) are not emitted. + +#version 450 + + +// === CONSTANTS === +const int LINE_INFO_ARROW = 1; +const int LINE_INFO_OVERFLOW = 2; +const float EDGE_SEPARATION_SCALE = 32; +const float VISIBLE_ARROW_DISTANCE = 100; +const float FLOAT_MULTIPLIER = 1024; + + +// === PUSH CONSTANTS === +layout(std140, push_constant) uniform HitTestPushConstant { + // If non-zero, use the texture to color the icon. + // Otherwise, use a unique color for hit testing. + // Offset is 64 as the projection matrix (in Line.vs) + // is before it in the pushConstant buffer. + // Note this is also read by Line.fs + layout(offset = 64) int drawHitTest; +} htpc; + + +// === UNIFORMS === +layout(std140, binding = 2) uniform UniformBlock { + mat4 pMatrix; + vec4 highlightColor; + float visibilityLow; + float visibilityHigh; + float directionMotion; + float alpha; +} ub; + + +// === PER PRIMITIVE DATA IN === +layout(lines) in; +layout(location = 0) in vec4 vpointColor[]; +layout(location = 1) flat in ivec4 gData[]; + + +// === PER PRIMITIVE DATA OUT === +layout(triangle_strip, max_vertices=22) out; +layout(location = 0) out vec4 pointColor; +layout(location = 1) flat out ivec4 fData; +layout(location = 2) out vec2 pointCoord; +layout(location = 3) flat out float lineLength; + + +void main() { + // Lines are explicitly not drawn if they have visibility <= 0. + // See au.gov.asd.tac.constellation.visual.opengl.task; + float visibility = vpointColor[0][3]; + if (visibility > max(ub.visibilityLow, 0) && (visibility <= ub.visibilityHigh || visibility > 1.0)) { + // The ends of the line. + vec4 end0 = gl_in[0].gl_Position; + vec4 end1 = gl_in[1].gl_Position; + + // If the line is selected then move it slightly forward so + // that it is drawn in front of unselected lines. + int seldim = gData[0][2]; + bool isSelected = (seldim & 1) != 0; + bool isDim = (seldim & 2) != 0; + if (isSelected) { + end0.z += 0.001; + end1.z += 0.001; + } + + float width = (gData[1].q >> 2) / FLOAT_MULTIPLIER; + float lineDistance = max(0, min(-end0.z, -end1.z)); + + // Always draw full lines and arrows if the width is greater than normal. + // (There's probably some optimisation at a further distance, but this will do for now.) + if (lineDistance <= VISIBLE_ARROW_DISTANCE || width > 1) { + float lineStyle = gData[1].q & 0x3; + + // The lines currently end at the centre of the 2*2 point sprite. + // We want to end them on the surface of a 1-radius sphere around the centre, + // so we subtract 1 from each end of the line. + // As the ends of the line approach each other, the ends should move towards the centres of the points. + lineLength = distance(end0, end1); + vec4 lineDirection = normalize(end1 - end0); + float arrowLength = (clamp(lineLength, 0.0, 3.0)) * 0.29167; + vec4 arrowVector = lineDirection * arrowLength; + + end1 -= arrowVector; + end0 += arrowVector; + + vec3 dir = normalize(cross(vec3(end0.xy - end1.xy, 0), vec3(0, 0, 1))); + float offset = gData[0].q / FLOAT_MULTIPLIER; + end0.xy += 2 * offset * dir.xy / 32; + end1.xy += 2 * offset * dir.xy / 32; + + vec4 halfWidth = vec4(dir.xy / 32, 0, 0); + int lineInfo0 = gData[0].t; + int lineInfo1 = gData[1].t; + + bool outgoing0 = (lineInfo0&LINE_INFO_ARROW) != 0; + bool outgoing1 = (lineInfo1&LINE_INFO_ARROW) != 0; + + // Adjust the width as specified by the graph attribute. + halfWidth *= width; + arrowVector *= width * 0.75; + + // If this line is selected and we're not drawing into the hit test buffer, + // or we've been told to draw it fatter (presumably to indicate many transactions in this edge), + // draw it fatter. + if ((isSelected && htpc.drawHitTest == 0) || (lineInfo0 & LINE_INFO_OVERFLOW) != 0 || (lineInfo1 & LINE_INFO_OVERFLOW) != 0) { + halfWidth *= 2; + arrowVector *= 1.5; + } + + // If we're drawing into the hit buffer, the ub.alpha is left alone, because hit testing depends on the edge id + // stored in the ub.alpha of the color. + // If we're drawing into the display buffer, vary the ub.alpha depending on the distance of the line from the camera. + // We use the nearest end of the line for the distance. + vec4 color0; + vec4 color1; + + if (htpc.drawHitTest == 0) { + color0 = vpointColor[0]; + color1 = vpointColor[1]; + + if (isSelected) { + color0 = ub.highlightColor; + color1 = ub.highlightColor; + } else if (isDim) { + color0 = vec4(0.25, 0.25, 0.25, 0.5); + color1 = color0; + } + + // This overwrites the visibility value in vpointcolor[][3]. + // TODO: The fade-out distance should depend on the current bounding box / camera distance. + float alphaFade = 1 - smoothstep(10, 500000, lineDistance); + color0.a = alphaFade * ub.alpha; + color1.a = alphaFade * ub.alpha; + } else { + color0 = vec4(0, 0, 0, 0); + color1 = color0; + } + + // Arrowheads and shafts need a different linestyle. + // The line shaft linestyle (dotted, dashed, etc) specifies that a part of the line will be discarded in the fragment shader. + // However, the arrowheads must always be drawn, so their line style must always be solid. + // We'll pass the lineStyle in fData.q (rather than a variable) so it can be primitive specific. + // + // If end1 has an outgoing arrow and end0 is not + // too far away then draw an arrow at end0. + if (outgoing1 && (end0.z>-100 || width>1)) { + // Draw a triangle to represent the arrow + pointColor = color0; + gl_Position = ub.pMatrix * end0; + gl_Position.y = -gl_Position.y; + fData = ivec4(gData[0].stp, 0); + pointCoord = vec2(0, 1); + EmitVertex(); + + pointColor = color0; + gl_Position = ub.pMatrix * (end0 - halfWidth * 4 + arrowVector); + gl_Position.y = -gl_Position.y; + fData = ivec4(gData[0].stp, 0); + pointCoord = vec2(-0.7, 0.5); + EmitVertex(); + + pointColor = color0; + gl_Position = ub.pMatrix * (end0 + halfWidth * 4 + arrowVector); + gl_Position.y = -gl_Position.y; + fData = ivec4(gData[0].stp, 0); + pointCoord = vec2(0.7, 0.5); + EmitVertex(); + + end0 += arrowVector; + + // If both ends have arrows then convert the triangle into a diamond. + if (outgoing0) { + pointColor = color0; + gl_Position = ub.pMatrix * (end0 + arrowVector * 0.5); + gl_Position.y = -gl_Position.y; + fData = ivec4(gData[0].stp, 0); + pointCoord = vec2(0, 0); + EmitVertex(); + + end0 += arrowVector * 0.4; + } + + EndPrimitive(); + } + + // If end0 has an outgoing arrow and end1 is not + // too far away then draw an arrow at end1. + if (outgoing0 && (end1.z > -100 || width > 1)) { + // Draw a triangle to represent the arrow + pointColor = color1; + gl_Position = ub.pMatrix * end1; + gl_Position.y = -gl_Position.y; + fData = ivec4(gData[0].stp, 0); + pointCoord = vec2(0, 1); + EmitVertex(); + + pointColor = color1; + gl_Position = ub.pMatrix * (end1 - halfWidth * 4 - arrowVector); + gl_Position.y = -gl_Position.y; + fData = ivec4(gData[0].stp, 0); + pointCoord = vec2(-0.7, 0.5); + EmitVertex(); + + pointColor = color1; + gl_Position = ub.pMatrix * (end1 + halfWidth * 4 - arrowVector); + gl_Position.y = -gl_Position.y; + fData = ivec4(gData[0].stp, 0); + pointCoord = vec2(0.7, 0.5); + EmitVertex(); + + end1 -= arrowVector; + + // If both ends have arrows then convert the triangle into a diamond. + if (outgoing1) { + pointColor = color1; + gl_Position = ub.pMatrix * (end1 - arrowVector * 0.5); + gl_Position.y = -gl_Position.y; + fData = ivec4(gData[0].stp, 0); + pointCoord = vec2(0, 0); + EmitVertex(); + + end1 -= arrowVector * 0.4; + } + + EndPrimitive(); + } + + // Recalculate the line length because we may have moved the end points + lineLength = length(end1 - end0); + + // Draw the shaft of the edge with the required line style. + pointColor = color0; + gl_Position = ub.pMatrix * (end0 + halfWidth); + gl_Position.y = -gl_Position.y; + fData = ivec4(gData[0].stp, lineStyle); + pointCoord = vec2(0.5, 0); + EmitVertex(); + + pointColor = color0; + gl_Position = ub.pMatrix * (end0 - halfWidth); + gl_Position.y = -gl_Position.y; + fData = ivec4(gData[0].stp, lineStyle); + pointCoord = vec2(-0.5, 0); + EmitVertex(); + + pointColor = color1; + gl_Position = ub.pMatrix * (end1 + halfWidth); + gl_Position.y = -gl_Position.y; + fData = ivec4(gData[0].stp, lineStyle); + pointCoord = vec2(0.5, lineLength); + EmitVertex(); + + pointColor = color1; + gl_Position = ub.pMatrix * (end1 - halfWidth); + gl_Position.y = -gl_Position.y; + fData = ivec4(gData[0].stp, lineStyle); + pointCoord = vec2(-0.5, lineLength); + EmitVertex(); + + EndPrimitive(); + + // Draw the motion bars + if (htpc.drawHitTest == 0 && ub.directionMotion != -1 && lineLength > 1 && outgoing0 != outgoing1) { + end0.z += 0.001; + end1.z += 0.001; + + float motionLen = lineLength / 32.0; + float motionPos = ub.directionMotion - (floor(ub.directionMotion / lineLength) * lineLength); + + if (outgoing1) { + motionPos = lineLength - motionPos; + motionLen = -motionLen; + } + + float motionStart = clamp(motionPos - motionLen, 0, lineLength); + float motionEnd = clamp(motionPos + motionLen, 0, lineLength); + + vec4 color; + if (htpc.drawHitTest == 0) { + color = mix(vpointColor[0], vpointColor[1], motionPos / (lineLength - motionLen)); + if (!isDim && !isSelected) { + float luminosity = 0.21 * color.r + 0.71 * color.g + 0.08 * color.b; + float adjust = luminosity > 0.5 ? -0.3 : 0.3; + color = clamp(vec4((adjust + color.r), (adjust + color.g), (adjust + color.b), color.a), 0, 1); + } + color.a = 1; + } else { + color = vec4(0, 0, 0, 0); + } + + pointColor = color; + gl_Position = ub.pMatrix * (end0 + halfWidth + motionStart * lineDirection); + gl_Position.y = -gl_Position.y; + fData = ivec4(gData[0].stp, lineStyle); + pointCoord = vec2(0.5, motionStart); + EmitVertex(); + + pointColor = color; + gl_Position = ub.pMatrix * (end0 - halfWidth + motionStart * lineDirection); + gl_Position.y = -gl_Position.y; + fData = ivec4(gData[0].stp, lineStyle); + pointCoord = vec2(-0.5, motionStart); + EmitVertex(); + + pointColor = color; + gl_Position = ub.pMatrix * (end0 + halfWidth + motionEnd * lineDirection); + gl_Position.y = -gl_Position.y; + fData = ivec4(gData[0].stp, lineStyle); + pointCoord = vec2(0.5, motionEnd); + EmitVertex(); + + pointColor = color; + gl_Position = ub.pMatrix * (end0 - halfWidth + motionEnd * lineDirection); + gl_Position.y = -gl_Position.y; + fData = ivec4(gData[0].stp, lineStyle); + pointCoord = vec2(-0.5, motionEnd); + EmitVertex(); + + EndPrimitive(); + } + } + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Line.vs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Line.vs new file mode 100644 index 0000000000..3fb99e48cc --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Line.vs @@ -0,0 +1,41 @@ +#version 450 + + +// === PUSH CONSTANT === +layout(std140, push_constant) uniform PushConstant { + mat4 mvMatrix; +} pc; + +// === UNIFORMS === +layout(std140, binding = 0) uniform UniformBlock { + float morphMix; +} ub; +layout(binding = 1) uniform samplerBuffer xyzTexture; + + +// === PER VERTEX DATA IN === +layout(location = 0) in vec4 vColor; +layout(location = 1) in ivec4 data; + + +// === PER VERTEX DATA OUT === +layout(location = 0) out vec4 vpointColor; +layout(location = 1) flat out ivec4 gData; + + +void main(void) { + // Pass the color to the fragment shader. + vpointColor = vColor; + gData = data; + + // Decode the index into the xyzTexture and the LINE_INFO from each other and put the LINE_INFO back. + int vxIndex = gData[1] / 4; + gData[1] = gData[1] - vxIndex * 4; + + int offset = vxIndex * 2; + vec3 v = texelFetch(xyzTexture, offset).stp; + vec3 vEnd = texelFetch(xyzTexture, offset + 1).stp; + vec3 mixedVertex = mix(v, vEnd, ub.morphMix); + + gl_Position = pc.mvMatrix * vec4(mixedVertex, 1); +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/LineLine.fs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/LineLine.fs new file mode 100644 index 0000000000..e389c29c78 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/LineLine.fs @@ -0,0 +1,43 @@ +#version 450 + + +// === CONSTANTS === +const int LINE_STYLE_SOLID = 0; +const int LINE_STYLE_DOTTED = 1; +const int LINE_STYLE_DASHED = 2; +const int LINE_STYLE_DIAMOND = 3; +const float LINE_DOT_SIZE = 0.3; + + +// === PER FRAGMENT DATA IN === +layout(location = 0) in vec4 pointColor; +layout(location = 1) flat in int hitTestId; +layout(location = 2) flat in int lineStyle; +layout(location = 3) in float pointCoord; +layout(location = 4) flat in float lineLength; + + +// === PER FRAGMENT DATA OUT === +layout(location = 0) out vec4 fragColor; + + +void main(void) { + // Line style. + if (lineStyle != LINE_STYLE_SOLID && lineLength > 0) { + float segmentSize = LINE_DOT_SIZE * (lineLength / (0.25 + lineLength)); + + if (lineStyle == LINE_STYLE_DOTTED || lineStyle == LINE_STYLE_DIAMOND) { + float seg = mod(pointCoord, 2 * segmentSize); + if(seg>(1 * segmentSize) && seg < (2 * segmentSize)) { + discard; + } + } else if (lineStyle == LINE_STYLE_DASHED) { + float seg = mod(pointCoord, 3 * segmentSize); + if (seg > (2 * segmentSize) && seg < (3 * segmentSize)) { + discard; + } + } + } + + fragColor = hitTestId == 0 ? pointColor : vec4(hitTestId, 0, 0, 1); +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/LineLine.gs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/LineLine.gs new file mode 100644 index 0000000000..2b7096d179 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/LineLine.gs @@ -0,0 +1,209 @@ +// LINE geometry shader. +// Points that are marked with the hidden indicator (color.a<0) are not emitted. +// +// The edge number is used to move the edge +// so multiple edges between two nodes are visible. An obvious way to do this is to slide all of +// the endpoints depending on the total number of edges. however, this required knowing the total +// number of edges, and we're trying not to pass extra data where possible. Instead, even numbered +// edges are moved one way and odd numbered edges are moved the other way. +// +// If an edge is drawn far away using triangle_strips, it doesn't look right: the triangles are drawn with artifacts that +// leave gaps in the lines. +// Therefore for distant edges we'll draw actual lines. + +#version 450 + + +// === CONSTANTS === +// Keep this in sync with SceneBatchStore. +const int LINE_INFO_ARROW = 1; +const int LINE_INFO_OVERFLOW = 2; +const float EDGE_SEPARATION_SCALE = 32; +const float VISIBLE_ARROW_DISTANCE = 100; +const float FLOAT_MULTIPLIER = 1024; + + +// === PUSH CONSTANTS === +layout(std140, push_constant) uniform HitTestPushConstant { + // If non-zero, use the texture to color the icon. + // Otherwise, use a unique color for hit testing. + // Offset is 64 as the projection matrix (in Line.vs) + // is before it in the pushConstant buffer. + layout(offset = 64) int drawHitTest; +} htpc; + + +// === UNIFORMS === +layout(std140, binding = 2) uniform UniformBlock { + mat4 pMatrix; + vec4 highlightColor; + float visibilityLow; + float visibilityHigh; + float directionMotion; + float alpha; +} ub; + + +// === PER VERTEX DATA IN === +layout(lines) in; +layout(location = 0) in vec4 vpointColor[]; +layout(location = 1) flat in ivec4 gData[]; + + +// === PER VERTEX DATA OUT === +layout(line_strip, max_vertices=4) out; +layout(location = 0) out vec4 pointColor; +layout(location = 1) flat out int hitTestId; +layout(location = 2) flat out int lineStyle; +layout(location = 3) out float pointCoord; +layout(location = 4) flat out float lineLength; + + +void main() { + // Lines are explicitly not drawn if they have visibility <= 0. + // See au.gov.asd.tac.constellation.visual.opengl.task; + float visibility = vpointColor[0][3]; + if (visibility > max(ub.visibilityLow, 0) && (visibility <= ub.visibilityHigh || visibility > 1.0)) { + // The ends of the line. + vec4 end0 = gl_in[0].gl_Position; + vec4 end1 = gl_in[1].gl_Position; + + // If the line is selected then move it slightly forward so + // that it is drawn in front of unselected lines. + int seldim = gData[0][2]; + bool isSelected = (seldim & 1) != 0; + bool isDim = (seldim & 2) != 0; + if (isSelected) { + end0.z += 0.001; + end1.z += 0.001; + } + + float width = (gData[1].q >> 2) / FLOAT_MULTIPLIER; + float lineDistance = max(0, min(-end0.z, -end1.z)); + + // Only draw line lines if the width is not greater than normal. + // (There's probably some optimisation at a further distance, but this will do for now.) + if (lineDistance > VISIBLE_ARROW_DISTANCE && width <= 1) { + lineStyle = gData[1].q & 0x3; + + // The lines currently end at the centre of the 2*2 point sprite. + // We want to end them on the surface of a 1-radius sphere around the centre, + // so we subtract 1 from each end of the line. + // As the ends of the line approach each other, the ends should move towards the centres of the points. + lineLength = distance(end0, end1); + vec4 lineDirection = normalize(end1 - end0); + float arrowLength = (clamp(lineLength, 0.0, 3.0)) * 0.29167; + vec4 arrowVector = lineDirection * arrowLength; + + end1 -= arrowVector; + end0 += arrowVector; + + vec3 dir = normalize(cross(vec3(end0.xy - end1.xy, 0), vec3(0, 0, 1))); + float offset = gData[0].q / FLOAT_MULTIPLIER; + end0.xy += 2 * offset * dir.xy / 32; + end1.xy += 2 * offset * dir.xy / 32; + + // If we're drawing into the hit buffer, the alpha is left alone, because hit testing depends on the edge id + // stored in the alpha of the color. + // If we're drawing into the display buffer, vary the alpha depending on the distance of the line from the camera. + // We use the nearest end of the line for the distance. + vec4 color0; + vec4 color1; + + // First, we tried passing the txId in the red element of the color0 and color1 vec4s. + // We crossed our fingers that color interpolation wouldn't screw up the value. + // However, this doesn't seem to be the case: sometimes an incorrect value ends up in + // the hit test framebuffer. + // Therefore, we'll pass the txId in a separate int that has no chance of being interpolated. + int hti; + + if (htpc.drawHitTest == 0) { + color0 = vpointColor[0]; + color1 = vpointColor[1]; + + // If this line is selected, and we're not drawing into the hit test buffer, + // draw it fatter. + if (isSelected) { + color0 = ub.highlightColor; + color1 = ub.highlightColor; + } else if (isDim) { + color0 = vec4(0.25, 0.25, 0.25, 0.5); + color1 = color0; + } + + // This overwrites the visibility value in vpointcolor[][3]. + // TODO: The fade-out distance should depend on the current bounding box / camera distance. + float lineAlpha = 1 - smoothstep(10, 500000, lineDistance); + color0.a = lineAlpha * ub.alpha; + color1.a = lineAlpha * ub.alpha; + + hti = 0; + } else { + // Color vec4s will be unused, but let's not leave things uninitialised. + color0 = vec4(0); + color1 = color0; + hti = -(gData[0].s + 1); + } + + lineLength = length(end1 - end0); + + pointColor = color0; + hitTestId = hti; + gl_Position = ub.pMatrix * end0; + gl_Position.y = -gl_Position.y; + pointCoord = 0; + EmitVertex(); + + pointColor = color1; + hitTestId = hti; + gl_Position = ub.pMatrix * end1; + gl_Position.y = -gl_Position.y; + pointCoord = lineLength; + EmitVertex(); + + EndPrimitive(); + + // Draw the motion bars. + bool arrow0 = (gData[0].t & LINE_INFO_ARROW) != 0; + bool arrow1 = (gData[1].t & LINE_INFO_ARROW) != 0; + if (htpc.drawHitTest == 0 && ub.directionMotion != -1 && lineLength > 1 && arrow0 != arrow1) { + end0.z += 0.001; + end1.z += 0.001; + + lineLength = length(end1 - end0); + float motionLen = lineLength / 32.0; + float motionPos = ub.directionMotion - (floor(ub.directionMotion / lineLength) * lineLength); + + if (arrow1) { + motionPos = lineLength - motionPos; + motionLen = -motionLen; + } + + float motionStart = clamp(motionPos - motionLen, 0, lineLength); + float motionEnd = clamp(motionPos + motionLen, 0, lineLength); + + vec4 color = mix(vpointColor[0], vpointColor[1], motionPos / (lineLength - motionLen)); + if (!isDim && !isSelected) { + float luminosity = 0.21 * color.r + 0.71 * color.g + 0.08 * color.b; + float adjust = luminosity > 0.5 ? -0.3 : 0.3; + color = clamp(vec4((adjust + color.r), (adjust + color.g), (adjust + color.b), color.a), 0, 1); + } + color.a = 1; + + pointColor = color; + gl_Position = ub.pMatrix * (end0 + motionStart * lineDirection); + gl_Position.y = -gl_Position.y; + pointCoord = 0; + EmitVertex(); + + pointColor = color; + gl_Position = ub.pMatrix * (end0 + motionEnd * lineDirection); + gl_Position.y = -gl_Position.y; + pointCoord = 0; + EmitVertex(); + + EndPrimitive(); + } + } + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Loop.fs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Loop.fs new file mode 100644 index 0000000000..c5defeb355 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Loop.fs @@ -0,0 +1,61 @@ +#version 450 + + +// === CONSTANTS === +const int LOOP_DIRECTED_INDEX = 2; +const int LOOP_UNDIRECTED_INDEX = 3; + + +// === PUSH CONSTANTS === +layout(std140, push_constant) uniform HitTestPushConstant { + // If non-zero, use the texture to color the icon. + // Otherwise, use a unique color for hit testing. + // Offset is 64 as the projection matrix (in Line.vs) + // is before it in the pushConstant buffer. + // Note this is also read by Line.gs + layout(offset = 64) int drawHitTest; +} pc; + + +// === UNIFORMS === +layout(binding = 3) uniform sampler2DArray images; + + +// === PER FRAGMENT DATA IN === +layout(location = 0) in vec4 pointColor; +layout(location = 1) flat in ivec4 fData; +layout(location = 2) noperspective centroid in vec3 textureCoords; + + +// === PER FRAGMENT DATA OUT === +layout(location = 0) out vec4 fragColor; + + +void main() { + int imgIx = fData.q; + + float iconOffsetX = float(imgIx % 8) / 8; + float iconOffsetY = float((imgIx / 8) % 8) / 8; + + vec4 pixel = texture(images, textureCoords); + fragColor = pixel; + if(fragColor.a == 0) { + discard; + } + + if (pc.drawHitTest == 0) { + int seldim = fData.p; + bool isSelected = (seldim & 1) != 0; + bool isDim = (seldim & 2) != 0; + + if (isSelected) { + fragColor = vec4(1, 0.1, 0.1, 1); + } else if (isDim) { + fragColor = vec4(0.25, 0.25, 0.25, 1); + } else { + fragColor.rgb *= pointColor.rgb; + } + } else { + fragColor = vec4(-(fData.s + 1), 0.0, 0.0, 1.0); + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Loop.gs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Loop.gs new file mode 100644 index 0000000000..9d6f63d5e6 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Loop.gs @@ -0,0 +1,105 @@ +#version 450 + + +// === CONSTANTS === +const float LOOP_SIZE = 0.5; + + +// === UNIFORMS === +layout(std140, binding = 2) uniform UniformBlock { + mat4 pMatrix; + float visibilityLow; + float visibilityHigh; + int iconsPerRowColumn; + int iconsPerLayer; + int atlas2DDimension; +} ub; + + +// === PER PRIMITIVE DATA IN === +layout(points) in; +layout(location = 0) in vec4 vpointColor[]; +layout(location = 1) flat in ivec4 gData[]; +layout(location = 2) flat in float nradius[]; + + +// === PER PRIMITIVE DATA OUT === +layout(triangle_strip, max_vertices=4) out; +layout(location = 0) out vec4 pointColor; +layout(location = 1) flat out ivec4 fData; +layout(location = 2) noperspective centroid out vec3 textureCoords; + + +void main() { + float visibility = vpointColor[0].q; + if(visibility > max(ub.visibilityLow, 0) && (visibility <= ub.visibilityHigh || visibility > 1.0)) { + + // === TEXTURE COORDS === // + float halfPixel = 0.5 / float(ub.atlas2DDimension); + float iconDimUVSpace = 1.0 / float(ub.iconsPerRowColumn); + +/* The shader needs to calculate texture coordinates that match the index in the texture, this + is the Java function that places icons: + public Vector3i IndexToTextureIndices(int index) { + return new Vector3i(index % iconsPerRowColumn, + (index % iconsPerLayer) / iconsPerRowColumn, + index / iconsPerLayer); + } +*/ + int icon = gData[0].q; + int u = icon % ub.iconsPerRowColumn; + int v = (icon % ub.iconsPerLayer) / ub.iconsPerRowColumn; + int w = icon / ub.iconsPerLayer; + vec3 iconOffset = vec3(float(u) / float(ub.iconsPerRowColumn), + float(v) / float(ub.iconsPerRowColumn), + float(w)); + + + // === VERTEX COORDS === // + vec4 vert = gl_in[0].gl_Position; + float sideRadius = nradius[0]; + vert += vec4(sideRadius, sideRadius, 0, 0); + + vec4 ul = ub.pMatrix * vec4(vert.x-LOOP_SIZE, vert.y - LOOP_SIZE, vert.z, vert.w); + vec4 ll = ub.pMatrix * vec4(vert.x-LOOP_SIZE, vert.y + LOOP_SIZE, vert.z, vert.w); + vec4 ur = ub.pMatrix * vec4(vert.x+LOOP_SIZE, vert.y - LOOP_SIZE, vert.z, vert.w); + vec4 lr = ub.pMatrix * vec4(vert.x+LOOP_SIZE, vert.y + LOOP_SIZE, vert.z, vert.w); + + + // === VERTEX EMISSION === // + + // Top left + pointColor = vpointColor[0]; + fData = gData[0]; + textureCoords = vec3(halfPixel, iconDimUVSpace - halfPixel, 0) + iconOffset; + gl_Position = ul; + gl_Position.y = -gl_Position.y; + EmitVertex(); + + // Bottom left + pointColor = vpointColor[0]; + fData = gData[0]; + textureCoords = vec3(halfPixel, halfPixel, 0) + iconOffset; + gl_Position = ll; + gl_Position.y = -gl_Position.y; + EmitVertex(); + + // Top right + pointColor = vpointColor[0]; + fData = gData[0]; + textureCoords = vec3(iconDimUVSpace - halfPixel, iconDimUVSpace - halfPixel, 0) + iconOffset; + gl_Position = ur; + gl_Position.y = -gl_Position.y; + EmitVertex(); + + // Bottom right + pointColor = vpointColor[0]; + fData = gData[0]; + textureCoords = vec3(iconDimUVSpace - halfPixel, halfPixel, 0) + iconOffset; + gl_Position = lr; + gl_Position.y = -gl_Position.y; + EmitVertex(); + + EndPrimitive(); + } +} \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Loop.vs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Loop.vs new file mode 100644 index 0000000000..897e68dc7b --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Loop.vs @@ -0,0 +1,46 @@ +#version 450 + + +// === PUSH CONSTANT === +layout(std140, push_constant) uniform PushConstant { + mat4 mvMatrix; +} pc; + + +// === UNIFORMS === +layout(std140, binding = 0) uniform UniformBlock { + float morphMix; +} ub; +layout(binding = 1) uniform samplerBuffer xyzTexture; + + +// === PER VERTEX DATA IN === +layout(location = 0) in vec4 vColor; +layout(location = 1) in ivec4 data; + + +// === PER VERTEX DATA OUT === +layout(location = 0) out vec4 vpointColor; +layout(location = 1) flat out ivec4 gData; +layout(location = 2) flat out float nradius; + + +void main(void) { + // Pass the color to the fragment shader. + vpointColor = vColor; + gData = data; + + // Get the index into the xyzTexture. + int vxIndex = gData.t; + + int offset = vxIndex * 2; + vec3 v = texelFetch(xyzTexture, offset).stp; + vec3 vEnd = texelFetch(xyzTexture, offset + 1).stp; + vec3 mixedVertex = mix(v, vEnd, ub.morphMix); + + gl_Position = pc.mvMatrix * vec4(mixedVertex, 1); + + // Get the side radius of the associated vertex and pass that through + // so the text is drawn relative to the node size. + nradius = mix(texelFetch(xyzTexture, offset).q, texelFetch(xyzTexture, offset + 1).q, ub.morphMix); +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/NodeLabel.vs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/NodeLabel.vs new file mode 100644 index 0000000000..aeb51e2dd2 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/NodeLabel.vs @@ -0,0 +1,146 @@ +// Draw labels for nodes. +// The labels are drawn glyph by glyph: each shader call draws one glyph. +#version 450 + + +// === CONSTANTS === +// The z distance from the camera at which a label is no longer visible +const float LABEL_VISIBLE_DISTANCE = 150; + +// Label scales are sent to the shader as integers between 1 and 64. Dividing by this factor converts them +// to float values between 0 and 4 as a proportion of the radius of the node. +const float LABEL_TO_NRADIUS_UNITS = 1/16.0; + +// A constant that is used to scale all labels consistently that was chosen long ago for aesthetic reasons +// and hence needs to remain to ensure consistency across graphs until a new version of the schema addressing this issue is released. +const float LABEL_AESTHETIC_SCALE = 5/8.0; + +// The gap between a node and the first label +const float LABEL_NODE_GAP = 0.2 / LABEL_AESTHETIC_SCALE; + +// The amount to darken the background of the labels with respect to the background of the graph. +// 1 means no darkening, 0 means completely black +const float BACKGROUND_DARKENING_FACTOR = 0.8; + + +// === PUSH CONSTANTS === +layout(std140, push_constant) uniform VertexPushConstant { + // Matrix to project from world coordinates to camera coordinates + mat4 mvMatrix; +} vpc; + + +// === UNIFORMS === +layout(std140, binding = 0) uniform UniformBlock { + // Each column is a node (bottom or top) label with the following structure: + // [0..2] rgb colour (note label colours do not habve an alpha) + // [3] label size + mat4 labelBottomInfo; + mat4 labelTopInfo; + + // Information from the graph's visual state + float morphMix; + float visibilityLow; + float visibilityHigh; + + // The index of the background glyph in the glyphInfo texture + int backgroundGlyphIndex; + + // Used to draw the label background. + vec4 backgroundColor; +} ub; +// .xyz = world coordinates of node. +// .a = radius of node. +layout(binding = 1) uniform samplerBuffer xyzTexture; + + +// === PER VERTEX DATA IN === +// [0..1] x and y offsets of this glyph from the top centre of the line of text +// [2] The visibility of this glyph (constant for a node, but easier to pass in the batch). +layout(location = 0) in vec3 glyphLocationData; + +// [0] the index of the glyph in the glyphInfoTexture +// [1] The index of the node containing this glyph in the xyzTexture +// [2] The total scale of the lines and their labels up to this point (< 0 if this is a glyph in a bottom label) +// [3] The label number in which this glyph occurs +layout(location = 1) in ivec4 graphLocationData; + + +// === PER VERTEX DATA OUT === +// Information about the texture location, colour and scale of the glyph +layout(location = 0) out int glyphIndex; +layout(location = 1) out vec4 labelColor; +layout(location = 2) out float glyphScale; +// The scaling factor if the glyph we are rendering is in fact the background for a line of text. +// This will be one in all other cases. +layout(location = 3) out float backgroundScalingFactor; +// A value describing the side of the background on which an indicator should be for connection labels. +// This will always be zero here because we don't draw indicators for node labels. +layout(location = 4) out int drawIndicator; +// Locations for placing the aforementioned indicator. Again they are unused here +layout(location = 5) out float drawIndicatorX; +layout(location = 6) out float drawIndicatorY; +// The depth of the label which is used to bring labels in front of connections. +// As this feature is not used for node labels, depth simply gets set to the z_position of the label. +layout(location = 7) out float depth; + + +void main(void) { + float glyphXOffset = glyphLocationData[0]; + float glyphYOffset = glyphLocationData[1]; + float glyphVis = glyphLocationData[2]; + + glyphIndex = int(graphLocationData[0]); + int nodeIndex = graphLocationData[1]; + int totalScale = graphLocationData[2]; + int labelNumber = graphLocationData[3]; + + // Find the xyz of the vertex that this glyph belongs to, + // specified by an offset into the xyzTexture buffer. + int offset = nodeIndex * 2; + vec4 v = texelFetch(xyzTexture, offset); + vec4 vEnd = texelFetch(xyzTexture, offset + 1); + vec4 mixedVertex = mix(v, vEnd, ub.morphMix); + + // Calculate the pixel coordinates of the vertex + vec4 nodeLocation = vpc.mvMatrix * vec4(mixedVertex.xyz, 1); + + // Get the radius of the associated vertex + float nradius = mixedVertex.w; + + + // Get the size and colour of this label from the relevant label information matrix + mat4 labelInfo = totalScale < 0 ? ub.labelBottomInfo : ub.labelTopInfo; + float labelScale = labelInfo[labelNumber][3] * LABEL_TO_NRADIUS_UNITS * LABEL_AESTHETIC_SCALE; + glyphScale = nradius * labelScale; + + // Determine visiblity of this label based both on the visibility of the associated node, and the fade out distance for labels. + float distance = -nodeLocation.z / nradius; + float alpha = (glyphVis > max(ub.visibilityLow, 0) && (glyphVis <= ub.visibilityHigh || glyphVis > 1.0)) ? + 1 - smoothstep((LABEL_VISIBLE_DISTANCE - 20) * glyphScale, LABEL_VISIBLE_DISTANCE * glyphScale, distance) : 0.0; + + // Set the colour (and background information if this glyph is the background) + if (glyphIndex == ub.backgroundGlyphIndex) { + labelColor = vec4(ub.backgroundColor.xyz * BACKGROUND_DARKENING_FACTOR, alpha); + backgroundScalingFactor = abs(2 * glyphXOffset); + } else { + labelColor = vec4(labelInfo[labelNumber].xyz, alpha); + backgroundScalingFactor = 1; + } + + // Set all values that only pertain to connection labels to their defaults + drawIndicator = 0; + drawIndicatorX = 0; + drawIndicatorY = 0; + depth = nodeLocation.z; + + // The total vertical offset of the label from the centre of the node. Note that the top label adjustment exists because we are measuring + // the offset to the top of the label, not the centre. + float topLabelAdjustment = labelScale * (1 + sign(totalScale)) / 2.0; + float labelYOffset = -nradius * ((totalScale * LABEL_TO_NRADIUS_UNITS * LABEL_AESTHETIC_SCALE) + (LABEL_NODE_GAP * sign(totalScale) + topLabelAdjustment)); + + // Calculate the pixel coordinates of the glyph's location on the graph + nodeLocation.x += glyphXOffset * glyphScale; + nodeLocation.y -= (glyphYOffset * glyphScale) + labelYOffset; + gl_Position = nodeLocation; +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThru.fs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThru.fs new file mode 100644 index 0000000000..bb554cf960 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThru.fs @@ -0,0 +1,14 @@ +#version 450 + + +// === PER FRAGMENT DATA IN +layout(location = 0) in vec4 fColor; + + +// === PER FRAGMENT DATA OUT +layout(location = 0) out vec4 fragColor; + + +void main(void) { + fragColor = fColor; +} \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThru.vs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThru.vs new file mode 100644 index 0000000000..a6762059db --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThru.vs @@ -0,0 +1,20 @@ +#version 450 + + +// === PUSH CONSTANT === +layout(std140, push_constant) uniform PushConstant { + mat4 mvpMatrix; +}pc; + +// === PER VERTEX DATA IN === +layout(location = 0) in vec3 vertex; +layout(location = 1) in vec4 color; + +// === PER VERTEX DATA OUT === +layout(location = 0) out vec4 gColor; + +void main(void) { + gColor = color; + gl_Position = pc.mvpMatrix * vec4(vertex, 1); + gl_Position.y = -gl_Position.y; +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThruLine.gs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThruLine.gs new file mode 100644 index 0000000000..17755b518c --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThruLine.gs @@ -0,0 +1,24 @@ +#version 450 + + +// === PER PRIMITIVE DATA IN === +layout(lines) in; +layout(location = 0) in vec4 gColor[]; + + +// === PER PRIMITIVE DATA OUT === +layout(line_strip, max_vertices=2) out; +layout(location = 0) out vec4 fColor; + + +void main() { + fColor = gColor[0]; + gl_Position = gl_in[0].gl_Position; + EmitVertex(); + + fColor = gColor[1]; + gl_Position = gl_in[1].gl_Position; + EmitVertex(); + + EndPrimitive(); +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThruPoint.fs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThruPoint.fs new file mode 100644 index 0000000000..cb14cf3069 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThruPoint.fs @@ -0,0 +1,13 @@ +#version 450 + + +// === PER TEXEL DATA OUT === +layout(location = 0) out vec4 fragColor; + + +void main(void) { + fragColor.x = clamp(gl_FragCoord.x / 1000.0, 0.2, 1.0); + fragColor.y = clamp(gl_FragCoord.y / 1000.0, 0.2, 1.0); + fragColor.z = clamp((fragColor.x + fragColor.y) / 2500.0, 0.2, 1.0); + fragColor.w = 1.0; +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThruPoint.vs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThruPoint.vs new file mode 100644 index 0000000000..7d331788c9 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThruPoint.vs @@ -0,0 +1,19 @@ +#version 450 + + +// === PUSH CONSTANT === +layout(std140, push_constant) uniform UniformBlock { + mat4 mvpMatrix; +}ub; + + +// === PER VERTEX DATA IN === +layout(location = 0) in vec3 vertex; + + +void main(void) { + gl_PointSize = 4.0; + + gl_Position = ub.mvpMatrix * vec4(vertex.x, vertex.y, vertex.z, 1); + gl_Position.y = -gl_Position.y; +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThruTriangle.gs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThruTriangle.gs new file mode 100644 index 0000000000..1f6b4433fd --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/PassThruTriangle.gs @@ -0,0 +1,28 @@ +#version 450 + + +// === PER PRIMITIVE DATA IN === +layout(triangles) in; +layout(location = 0) in vec4 gColor[]; + + +// === PER PRIMITIVE DATA OUT === +layout(triangle_strip, max_vertices=3) out; +layout(location = 0) out vec4 fColor; + + +void main() { + fColor = gColor[0]; + gl_Position = gl_in[0].gl_Position; + EmitVertex(); + + fColor = gColor[1]; + gl_Position = gl_in[1].gl_Position; + EmitVertex(); + + fColor = gColor[2]; + gl_Position = gl_in[2].gl_Position; + EmitVertex(); + + EndPrimitive(); +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Plane.fs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Plane.fs new file mode 100644 index 0000000000..db9e370d5e --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Plane.fs @@ -0,0 +1,28 @@ +#version 450 + + +// === UNIFORMS === +layout(binding = 1) uniform sampler2DArray images; + + +// === PER FRAGMENT DATA IN === +layout(location = 0) flat in float tex; +layout(location = 1) in vec2 pointCoord; +layout(location = 2) flat in vec2 width_height_frac; + + +// === PER FRAGMENT DATA OUT === +layout(location = 0) out vec4 fragColor; + + +void main(void) { + float x = pointCoord.x; + float y = pointCoord.y; + vec4 pixel = texture(images, vec3(x * width_height_frac[0], y * width_height_frac[1], tex)); + float alpha = min(pixel.a, 0.75); + if (alpha > 0) { + fragColor = vec4(pixel.rgb, alpha); + } else { + discard; + } +} \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Plane.gs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Plane.gs new file mode 100644 index 0000000000..9bdb787f40 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Plane.gs @@ -0,0 +1,39 @@ +#version 450 + + +// === PER PRIMITIVE DATA IN === +layout(triangles) in; +layout(location = 0) in vec4 gData[]; + + +// === PER PRIMITIVE DATA IN === +layout(triangle_strip, max_vertices=3) out; +layout(location = 0) flat out float tex; +layout(location = 1) out vec2 pointCoord; +layout(location = 2) flat out vec2 width_height_frac; + + +void main() { + float visible = gData[0].p; + if (visible > 0) { + width_height_frac = vec2(gData[1].p, gData[2].p); + tex = gData[0].q; + pointCoord = gData[0].xy; + gl_Position = gl_in[0].gl_Position; + EmitVertex(); + + width_height_frac = vec2(gData[1].p, gData[2].p); + tex = gData[0].q; + pointCoord = gData[1].xy; + gl_Position = gl_in[1].gl_Position; + EmitVertex(); + + width_height_frac = vec2(gData[1].p, gData[2].p); + tex = gData[0].q; + pointCoord = gData[2].xy; + gl_Position = gl_in[2].gl_Position; + EmitVertex(); + + EndPrimitive(); + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Plane.vs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Plane.vs new file mode 100644 index 0000000000..5951779be4 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/Plane.vs @@ -0,0 +1,22 @@ +#version 450 + + +// === UNIFORMS +layout(std140, push_constant) uniform UniformBlock { + mat4 mvpMatrix; +} ub; + + +// === PER VERTEX DATA IN +layout(location = 0) in vec3 vertex; +layout(location = 1) in vec4 data; + + +// === PER VERTEX DATA OUT +layout(location = 0) out vec4 gData; + +void main(void) { + gData = data; + gl_Position = ub.mvpMatrix * vec4(vertex, 1); + gl_Position.y = -gl_Position.y; +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/SimpleIcon.fs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/SimpleIcon.fs new file mode 100644 index 0000000000..8d8d341de2 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/SimpleIcon.fs @@ -0,0 +1,27 @@ +#version 450 + + +// === UNIFORMS === +// Note this is an opaque uniform, ie not data we pass in but an object this shader can reference. This +// array image is generated by CVKIconTextureAtlas. +layout(binding = 1) uniform sampler2DArray images; + + +// === PER FRAGMENT DATA IN === +layout(location = 0) flat in mat4 iconColor; +layout(location = 4) noperspective centroid in vec3 textureCoords; + + +// === PER FRAGMENT DATA OUT === +layout(location = 0) out vec4 fragColor; + + +void main(void) { + fragColor = iconColor * texture(images, textureCoords); + + // Discarding only when fragColor.a==0.0 means that some "nearly transparent" pixels get drawn, which causes weird see-through + // artifacts around the edges. Instead we'll discard nearly transparent pixels as well at an arbitrary cut-off point. + if(fragColor.a < 0.1) { + discard; + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/SimpleIcon.gs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/SimpleIcon.gs new file mode 100644 index 0000000000..303e2c6d35 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/SimpleIcon.gs @@ -0,0 +1,149 @@ +// Icon geometry shader. +// Icons that are marked with the hidden indicator (color.a<0) are not emitted. +// +// The [gf]DisplayColor passes the highlight color through to the fragment shader. + +#version 450 + + +// === CONSTANTS === +const int ICON_BITS = 16; +const int ICON_MASK = 0xffff; +const float TEXTURE_SIZE = 0.125; +const int TRANSPARENT_ICON = 5; +const mat4 IDENTITY_MATRIX = mat4( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 +); + + +// === UNIFORMS === +layout(std140, binding = 0) uniform UniformBlock { + mat4 pMatrix; + float pixelDensity; + float pScale; + int iconsPerRowColumn; + int iconsPerLayer; + int atlas2DDimension; +} ub; + + +// === PER PRIMITIVE DATA IN === +layout(points) in; +layout(location = 0) flat in ivec2 gData[]; +layout(location = 1) flat in mat4 gBackgroundIconColor[]; +layout(location = 5) flat in float gRadius[]; + + +// === PER PRIMITIVE DATA OUT === +layout(triangle_strip, max_vertices=28) out; +layout(location = 0) flat out mat4 iconColor; +layout(location = 4) noperspective centroid out vec3 textureCoords; + + +// === FILE SCOPE VARS === +vec4 vert; +float iconDimUVSpace; //the width and height of an icon in UV space, 0.0-1.0 +float halfPixel; + + +/* +Compared to the OpenGL version of this shader the Y is flipped. + +OpenGL +^ Y +| +| +.----> X + +Vulkan +.----> X +| +| +V Y +*/ + + +void drawIcon(float x, float y, float radius, int icon, mat4 color) { + if (icon != TRANSPARENT_ICON) { + +/* The shader needs to calculate texture coordinates that match the index in the texture, this + is the Java function that places icons: + public Vector3i IndexToTextureIndices(int index) { + return new Vector3i(index % iconsPerRowColumn, + (index % iconsPerLayer) / iconsPerRowColumn, + index / iconsPerLayer); + } +*/ + int u = icon % ub.iconsPerRowColumn; + int v = (icon % ub.iconsPerLayer) / ub.iconsPerRowColumn; + int w = icon / ub.iconsPerLayer; + vec3 iconOffset = vec3(float(u) / float(ub.iconsPerRowColumn), + float(v) / float(ub.iconsPerRowColumn), + float(w)); + + // Top left + gl_Position = vert + (ub.pScale * ub.pMatrix * vec4(x, y, 0, 0)); + iconColor = color; + textureCoords = vec3(iconDimUVSpace - halfPixel, iconDimUVSpace - halfPixel, 0) + iconOffset; + EmitVertex(); + + // Bottom left + gl_Position = vert + (ub.pScale * ub.pMatrix * vec4(x, y + radius, 0, 0)); + iconColor = color; + textureCoords = vec3(iconDimUVSpace - halfPixel, halfPixel, 0) + iconOffset; + EmitVertex(); + + // Top right + gl_Position = vert + (ub.pScale * ub.pMatrix * vec4(x + radius, y, 0, 0)); + iconColor = color; + textureCoords = vec3(halfPixel, iconDimUVSpace - halfPixel, 0) + iconOffset; + EmitVertex(); + + // Bottom right + gl_Position = vert + (ub.pScale * ub.pMatrix * vec4(x + radius, y + radius, 0, 0)); + iconColor = color; + textureCoords = vec3(halfPixel, halfPixel, 0) + iconOffset; + EmitVertex(); + + EndPrimitive(); + } +} + +void main() { + float sideRadius = gRadius[0]; + if (sideRadius > 0) { + + halfPixel = 0.5 / float(ub.atlas2DDimension); + iconDimUVSpace = 1.0 / float(ub.iconsPerRowColumn); + + // Get the position of the vertex + vert = gl_in[0].gl_Position; + + int bgIcon = (gData[0][0] >> ICON_BITS) & ICON_MASK; + + float iconPixelRadius = sideRadius * ub.pixelDensity / -vert.z; + if (iconPixelRadius < 1 && bgIcon != TRANSPARENT_ICON) { + mat4 backgroundIconColor = gBackgroundIconColor[0]; + backgroundIconColor[3][3] = max(smoothstep(0.0, 1.0, iconPixelRadius), 0.7); + drawIcon(-sideRadius, -sideRadius, 2 * sideRadius, bgIcon, backgroundIconColor); + } else { + + // Draw the background icon + mat4 iconColor = gBackgroundIconColor[0]; + drawIcon(-sideRadius, -sideRadius, 2 * sideRadius, bgIcon, iconColor); + + // Draw the foreground icon + int fgIcon = gData[0][0] & ICON_MASK; + if (bgIcon != TRANSPARENT_ICON) { + iconColor[3][3] = smoothstep(1.0, 5.0, iconPixelRadius); + drawIcon(-sideRadius, -sideRadius, 2 * sideRadius, fgIcon, iconColor); + } else { + drawIcon(-sideRadius, -sideRadius, 2 * sideRadius, fgIcon, iconColor); + iconColor[3][3] = smoothstep(1.0, 5.0, iconPixelRadius); + } + } + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/SimpleIcon.vs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/SimpleIcon.vs new file mode 100644 index 0000000000..393d9ab4fe --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/SimpleIcon.vs @@ -0,0 +1,48 @@ +// Draw icons as point sprites. +// Make points that are nearer to the camera bigger. +#version 450 + + +// === UNIFORMS === +layout(std140, push_constant) uniform UniformBlock { + mat4 mvMatrix; + float visibilityLow; + float visibilityHigh; +} ub; + + +// === PER VERTEX DATA IN === +// This is a set of data for this vertex. +// TODO_TT: maybe we can remove data from the vert shader altogether as it is +// unused and in the geo shader only the first element of data is used. +layout(location = 0) in ivec2 data; +layout(location = 1) in vec4 backgroundIconColor; + + +// === PER VERTEX DATA OUT === +layout(location = 0) out ivec2 gData; // {icon indexes (encoded to int), digit index * 4) +layout(location = 1) out mat4 gBackgroundIconColor; //mat4 eats 4 slots +layout(location = 5) flat out float gRadius; + + +void main(void) { + // Pass stuff to the next shader. + gData = data; + gBackgroundIconColor = mat4( + backgroundIconColor.r, 0, 0, 0, + 0, backgroundIconColor.g, 0, 0, + 0, 0, backgroundIconColor.b, 0, + 0, 0, 0, 1 + ); + + vec3 digitPosition = vec3(data[1], 0, 0); + + float visibility = backgroundIconColor.a; + if(visibility > max(ub.visibilityLow, 0.0) && (visibility <= ub.visibilityHigh || visibility > 1.0)) { + gRadius = 1; + } else { + gRadius = -1; + } + + gl_Position = ub.mvMatrix * vec4(digitPosition, 1); +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/VertexIcon.fs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/VertexIcon.fs new file mode 100644 index 0000000000..e37917a2b0 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/VertexIcon.fs @@ -0,0 +1,50 @@ +// Color icons in one of two ways. +// +// If drawHitTest is false, draw a texture using pointColor[3]. +// If drawHitTest is true, convert pointColor[2] to unique color to be used for hit testing. +#version 450 + + +// === PUSH CONSTANTS === +layout(std140, push_constant) uniform HitTestPushConstant { + // If non-zero, use the texture to color the icon. + // Otherwise, use a unique color for hit testing. + // Offset is 64 as the MV matrix (in VertexIcon.vs) + // is before it in the pushConstant buffer. + layout(offset = 64) int drawHitTest; +} htpc; + + +// === UNIFORMS === +// Note this is an opaque uniform, ie not data we pass in but an object this shader can reference. This +// array image is generated by CVKIconTextureAtlas. +layout(binding = 4) uniform sampler2DArray images; + + +// === PER FRAGMENT DATA IN === +layout(location = 0) flat in mat4 iconColor; +layout(location = 4) noperspective centroid in vec3 textureCoords; +layout(location = 5) flat in vec4 hitBufferValue; + + +// === PER FRAGMENT DATA OUT === +layout(location = 0) out vec4 fragColor; + + +void main(void) { + if (htpc.drawHitTest == 0) { + fragColor = iconColor * texture(images, textureCoords); + + // Discarding only when fragColor.a==0.0 means that some "nearly transparent" pixels get drawn, which causes weird see-through + // artifacts around the edges. Instead we'll discard nearly transparent pixels as well at an arbitrary cut-off point. + if(fragColor.a < 0.1) { + discard; + } + } else { + if (iconColor[3][3] * texture(images, textureCoords).a > 0.1) { + fragColor = hitBufferValue; + } else { + discard; + } + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/VertexIcon.gs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/VertexIcon.gs new file mode 100644 index 0000000000..a855f9b608 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/VertexIcon.gs @@ -0,0 +1,219 @@ +// Icon geometry shader. +// Icons that are marked with the hidden indicator (color.a<0) are not emitted. +// +// The [gf]DisplayColor passes the highlight color through to the fragment shader. + +#version 450 + + +// === CONSTANTS === +const int ICON_BITS = 16; +const int ICON_MASK = 0xffff; +const int SELECTED_MASK = 1; +const int DIM_MASK = 2; +const int HIGHLIGHT_ICON = 0; +const int TRANSPARENT_ICON = 5; + +// Magic numbers, what could go wrong... +const mat4 DIM_MATRIX = 0.5 * mat4( + 0.21, 0.21, 0.21, 0.00, + 0.71, 0.71, 0.71, 0.00, + 0.08, 0.08, 0.08, 0.00, + 0.00, 0.00, 0.00, 1.00 +); +const mat4 IDENTITY_MATRIX = mat4(1.0); + + +// === PUSH CONSTANTS === +layout(std140, push_constant) uniform HitTestPushConstant { + // Offset is 64 as the MV matrix (in VertexIcon.vs) + // is before it in the pushConstant buffer. + layout(offset = 64) int drawHitTest; +} htpc; + + +// === UNIFORMS === +layout(std140, binding = 2) uniform UniformBlock { + int iconsPerRowColumn; + int iconsPerLayer; + int atlas2DDimension; + + float pixelDensity; + mat4 highlightColor; + mat4 pMatrix; +} ub; +layout(binding = 3) uniform isamplerBuffer flags; + + +// === PER PRIMITIVE DATA IN === +layout(points) in; +layout(location = 0) flat in ivec4 gData[]; +layout(location = 1) in mat4 gBackgroundIconColor[]; +layout(location = 5) flat in int vxPosition[]; + +// The radius of the vertex +layout(location = 6) flat in float gRadius[]; + + +// === PER PRIMITIVE DATA OUT === +layout(triangle_strip, max_vertices=28) out; +layout(location = 0) flat out mat4 iconColor; +layout(location = 4) noperspective centroid out vec3 textureCoords; +layout(location = 5) flat out vec4 hitBufferValue; + + +// === FILE SCOPE VARS === +vec4 vert; +vec4 hbv; +float iconDimUVSpace; //the width and height of an icon in UV space, 0.0-1.0 +float halfPixel; + + +// NOTE: this shader has been modified for Vulkan. The uniforms need to be explicitly +// bound to slots and locations that match descriptors defined in the CVKIconsRenderable +// class. Additionally the Y goes down the screen in Vulkan as by default it uses a +// right handed coordinate system (take your index finger as Z pointing away from you, your +// long finger is x pointing to the right, which leaves your thumb as y pointing down). +// OpenGL uses a left hand system with Y going up. That means to convert a GL shader for +// Vulkan we need to modify the Y and the V texture coordinate. For this shader we take the +// negative y when processing the graph access vertices. Texture coordinates don't care about +// the LHS or RHS used by the vertices. +void drawIcon(float x, float y, float radius, int icon, mat4 color) { + + if (icon != TRANSPARENT_ICON) { + +/* The shader needs to calculate texture coordinates that match the index in the texture, this + is the Java function that places icons: + public Vector3i IndexToTextureIndices(int index) { + return new Vector3i(index % iconsPerRowColumn, + (index % iconsPerLayer) / iconsPerRowColumn, + index / iconsPerLayer); + } +*/ + int u = icon % ub.iconsPerRowColumn; + int v = (icon % ub.iconsPerLayer) / ub.iconsPerRowColumn; + int w = icon / ub.iconsPerLayer; + vec3 iconOffset = vec3(float(u) / float(ub.iconsPerRowColumn), + float(v) / float(ub.iconsPerRowColumn), + float(w)); + + // Bottom Left + gl_Position = ub.pMatrix * vec4(vert.x + x, vert.y + y + radius, vert.z, vert.w); + gl_Position.y = -gl_Position.y; + iconColor = color; + hitBufferValue = hbv; + textureCoords = vec3(halfPixel, halfPixel, 0) + iconOffset; + EmitVertex(); + + // Top Left + gl_Position = ub.pMatrix * vec4(vert.x + x, vert.y + y, vert.z, vert.w); + gl_Position.y = -gl_Position.y; + iconColor = color; + hitBufferValue = hbv; + textureCoords = vec3(halfPixel, iconDimUVSpace - halfPixel, 0) + iconOffset; + EmitVertex(); + + // Bottom Right + gl_Position = ub.pMatrix * vec4(vert.x + x + radius, vert.y + y + radius, vert.z, vert.w); + gl_Position.y = -gl_Position.y; + iconColor = color; + hitBufferValue = hbv; + textureCoords = vec3(iconDimUVSpace - halfPixel, halfPixel, 0) + iconOffset; + EmitVertex(); + + // Top Right + gl_Position = ub.pMatrix * vec4(vert.x + x + radius, vert.y + y, vert.z, vert.w); + gl_Position.y = -gl_Position.y; + iconColor = color; + hitBufferValue = hbv; + textureCoords = vec3(iconDimUVSpace - halfPixel, iconDimUVSpace - halfPixel, 0) + iconOffset; + EmitVertex(); + + EndPrimitive(); + } +} + +void main() { + // The atlas texture size can change as more icons are added so we need to do a little calculation + halfPixel = 0.5 / float(ub.atlas2DDimension); + iconDimUVSpace = 1.0 / float(ub.iconsPerRowColumn); + + // Nodes are explicitly not drawn if they have visibility <= 0. + // See au.gov.asd.tac.constellation.visual.opengl.task.NodeHider.java. + + //TT: vertex shader will encode this to either 1 if visible or -1 otherwise + float sideRadius = gRadius[0]; + if (sideRadius > 0) { + + // Get the position of the vertex + vert = gl_in[0].gl_Position; + + // Calculate the hit buffer value + hbv = vec4(gData[0][3] + 1, 0, 0, 1); + + // Get the flags associated with this vertex + // selected, dimmed etc. + int fd = texelFetch(flags, vxPosition[0]).s; + + // If the vertex is selected then move it slightly forward so + // that it is drawn in front of unselected vertices. + if((fd & SELECTED_MASK) != 0) { + // Set a minimum size for selected vertices. + float ddist = 800; + if(vert.z < -ddist) { + sideRadius *= 1 - ((ddist+vert.z)/ddist); + } + + vert.z += 0.001; + } + + int bgIcon = (gData[0][0] >> ICON_BITS) & ICON_MASK; + + float iconPixelRadius = sideRadius * ub.pixelDensity / -vert.z; + if (iconPixelRadius < 1 && bgIcon != TRANSPARENT_ICON) { + mat4 backgroundIconColor = gBackgroundIconColor[0]; + backgroundIconColor[3][3] = max(smoothstep(0.0, 1.0, iconPixelRadius), 0.7); + if ((fd & DIM_MASK) != 0) { + backgroundIconColor = DIM_MATRIX * backgroundIconColor; + } + drawIcon(-sideRadius, -sideRadius, 2 * sideRadius, bgIcon, backgroundIconColor); + + } else { + + // Draw the background icon + mat4 iconColor = gBackgroundIconColor[0]; + if ((fd & DIM_MASK) != 0) { + iconColor = DIM_MATRIX * iconColor; + } + drawIcon(-sideRadius, -sideRadius, 2 * sideRadius, bgIcon, iconColor); + + // Draw the foreground icon + int fgIcon = gData[0][0] & ICON_MASK; + if ((fd & DIM_MASK) != 0) { + iconColor = DIM_MATRIX; + } else { + iconColor = IDENTITY_MATRIX; + } + if (bgIcon != TRANSPARENT_ICON) { + iconColor[3][3] = smoothstep(1.0, 5.0, iconPixelRadius); + drawIcon(-sideRadius, -sideRadius, 2 * sideRadius, fgIcon, iconColor); + } else { + drawIcon(-sideRadius, -sideRadius, 2 * sideRadius, fgIcon, iconColor); + iconColor[3][3] = smoothstep(1.0, 5.0, iconPixelRadius); + } + + // Draw the decorators + float decoratorRadius = sideRadius * 2/3; + float decoratorOffset = sideRadius / 3; + drawIcon(-sideRadius, decoratorOffset, decoratorRadius, gData[0][1] & ICON_MASK, iconColor); + drawIcon(-sideRadius, -sideRadius, decoratorRadius, (gData[0][1] >> ICON_BITS) & ICON_MASK, iconColor); + drawIcon(decoratorOffset, -sideRadius, decoratorRadius, gData[0][2] & ICON_MASK, iconColor); + drawIcon(decoratorOffset, decoratorOffset, decoratorRadius, (gData[0][2] >> ICON_BITS) & ICON_MASK, iconColor); + } + + // If the vertex is selected then draw the highlight icon + if ((htpc.drawHitTest == 0) && (fd & SELECTED_MASK) != 0) { + drawIcon(-sideRadius, -sideRadius, 2 * sideRadius, HIGHLIGHT_ICON, ub.highlightColor); + } + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/VertexIcon.vs b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/VertexIcon.vs new file mode 100644 index 0000000000..e7d7923f36 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/VertexIcon.vs @@ -0,0 +1,57 @@ +// Draw icons as point sprites. +// Make points that are nearer to the camera bigger. +#version 450 + + +// === PUSH CONSTANTS === +layout(std140, push_constant) uniform VertexPushConstant { + mat4 mvMatrix; +} vpc; + + +// === UNIFORMS === +layout(std140, binding = 0) uniform UniformBlock { + float morphMix; + float visibilityLow; + float visibilityHigh; +} ub; +layout(binding = 1) uniform samplerBuffer xyzTexture; + + +// === PER VERTEX DATA IN === +layout(location = 0) in vec4 backgroundIconColor; +layout(location = 1) in ivec4 data; + + +// === PER VERTEX DATA OUT === +layout(location = 0) flat out ivec4 gData; +layout(location = 1) out mat4 gBackgroundIconColor; //mat4 eats 4 slots +layout(location = 5) flat out int vxPosition; +layout(location = 6) flat out float gRadius; + + +void main(void) { + // Pass stuff to the next shader. + gData = data; + gBackgroundIconColor = mat4( + backgroundIconColor.r, 0, 0, 0, + 0, backgroundIconColor.g, 0, 0, + 0, 0, backgroundIconColor.b, 0, + 0, 0, 0, 1 + ); + vxPosition = gl_VertexIndex; + + int index = gl_VertexIndex * 2; + vec4 v0 = texelFetch(xyzTexture, index); + vec4 v1 = texelFetch(xyzTexture, index+1); + vec3 mixedVertex = mix(v0.xyz, v1.xyz, ub.morphMix); + + float visibility = backgroundIconColor.a; + if(visibility > max(ub.visibilityLow, 0) && (visibility <= ub.visibilityHigh || visibility > 1.0)) { + gRadius = v0.w; + } else { + gRadius = -1; + } + + gl_Position = vpc.mvMatrix * vec4(mixedVertex, 1); +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compile_shaders.py b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compile_shaders.py new file mode 100644 index 0000000000..ffce1ab342 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compile_shaders.py @@ -0,0 +1,93 @@ +""" +Compile GLSL shaders in a directory to SPIR-V bytecode. + +Copyright 2010-2020 Australian Signals Directorate +""" + +import os +import subprocess +import hashlib +from os import walk, environ +from sys import platform, stdout, stderr + + +def compile_shader(path_to_glslc, shader_path, stage, out_name, md5_name): + """ + + -fauto-map-locations SPIR-V and later GLSL versions require inputs and outputs to be bound to an attribute location, this just assigns them automagically + -fauto-bind-uniforms SPIR-V and later GLSL versions require uniforms to have explicit binding, this just assigns them automagically + -O optimises performance over size + -o is the output file + + :param path_to_glslc: + :param shader_path: + :param out_name: + :return: + """ + cmd_line = '{0} --target-spv=spv1.0 -fauto-map-locations -fauto-bind-uniforms -O -o {1} -fshader-stage={2} {3}'\ + .format(path_to_glslc, + out_name, + stage, + shader_path) + result = subprocess.call(cmd_line, + stdin=None, + stdout=stdout, + stderr=stderr) + + # Write MD5 of source + md5_file = open(md5_name, 'wb') + + hash_md5 = hashlib.md5() + with open(shader_path, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + hash_md5.update(chunk) + md5_file.write(hash_md5.digest()) + md5_file.close() + + if result == 0: + print('Compiled {0}, MD5: {1}'.format(shader_path, hash_md5.hexdigest())) + else: + print(result) + + +def compile_shaders(src_dir, dst_dir): + if not os.path.exists(dst_dir): + os.makedirs(dst_dir) + + # Check the GLSL compiler is available + if platform == 'win32': + VK_SDK_PATH = environ.get('VK_SDK_PATH') + if VK_SDK_PATH and os.path.exists(VK_SDK_PATH): + path_to_glslc = os.path.join(VK_SDK_PATH, 'bin', 'glslc.exe') + if os.path.exists(path_to_glslc): + for (dir_path, dir_names, file_names) in walk(src_dir): + for file_name in file_names: + # exclude this script + if not __file__.endswith(file_name): + if file_name.endswith('.fs') or file_name.endswith('.frag'): + stage = 'frag' + elif file_name.endswith('.gs') or file_name.endswith('.geom'): + stage = 'geom' + elif file_name.endswith('.vs') or file_name.endswith('.vert'): + stage = 'vert' + else: + print('WARNING: {0} skipped, supported file extensions: [.gs, .fs, .vs]'.format(file_name)) + continue + + in_name = os.path.join(dir_path, file_name) + out_name = os.path.join(dst_dir, file_name) + '.spv' + md5_name = os.path.join(dst_dir, file_name) + '.md5' + compile_shader(path_to_glslc, in_name, stage, out_name, md5_name) + # Don't recurse + break; + else: + raise Exception('{0} not found.'.format(path_to_glslc)) + else: + raise Exception('VK_SDK_PATH not found. Vulkan SDK needed to compile shaders.') + + else: + raise NotImplementedError('Not implemented on this OS. It should be simple to add. Add the Vulkan SDK path and you should be gtg.') + + +if __name__ == '__main__': + compile_shaders('.', 'compiled') \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.fs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.fs.md5 new file mode 100644 index 0000000000..551dafae11 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.fs.md5 @@ -0,0 +1 @@ +÷Fp˜%º_Óïì$é'' \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.fs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.fs.spv new file mode 100644 index 0000000000..fe64a424fd Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.fs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.gs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.gs.md5 new file mode 100644 index 0000000000..17513ce8bd --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.gs.md5 @@ -0,0 +1 @@ ++†|oÑ$¥e^!ƒª!“´¨ \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.gs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.gs.spv new file mode 100644 index 0000000000..d30c40556b Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.gs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.vs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.vs.md5 new file mode 100644 index 0000000000..768603faa0 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.vs.md5 @@ -0,0 +1 @@ +C_[o‡J¬ÞØ]ÚÍD橀 \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.vs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.vs.spv new file mode 100644 index 0000000000..8a217bd149 Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Blaze.vs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/ConnectionLabel.vs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/ConnectionLabel.vs.md5 new file mode 100644 index 0000000000..66f0dfbd37 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/ConnectionLabel.vs.md5 @@ -0,0 +1 @@ +¨}Ï mÕ…&ÚÜù{¯í \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/ConnectionLabel.vs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/ConnectionLabel.vs.spv new file mode 100644 index 0000000000..75ec7cd616 Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/ConnectionLabel.vs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Label.fs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Label.fs.md5 new file mode 100644 index 0000000000..8460ae83f8 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Label.fs.md5 @@ -0,0 +1 @@ +râÿ½¥dX.Jq<ÖŸS¤ \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Label.fs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Label.fs.spv new file mode 100644 index 0000000000..3ff7453c3a Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Label.fs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Label.gs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Label.gs.md5 new file mode 100644 index 0000000000..a224a8b0dd --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Label.gs.md5 @@ -0,0 +1 @@ +Q^´¬÷ñX.á*`%Ê˹ \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Label.gs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Label.gs.spv new file mode 100644 index 0000000000..6327229909 Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Label.gs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.fs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.fs.md5 new file mode 100644 index 0000000000..a1b90d3e65 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.fs.md5 @@ -0,0 +1 @@ +`ñ¤;õy0qO µÀÿ² \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.fs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.fs.spv new file mode 100644 index 0000000000..2e392e6098 Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.fs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.gs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.gs.md5 new file mode 100644 index 0000000000..5206b2dfd5 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.gs.md5 @@ -0,0 +1 @@ +³œpf˜Åe Ó┓Ø@ \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.gs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.gs.spv new file mode 100644 index 0000000000..3452fc4c5f Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.gs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.vs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.vs.md5 new file mode 100644 index 0000000000..2cdc0ab85f --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.vs.md5 @@ -0,0 +1 @@ +E,µÒ/V\W…gVøØÃV \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.vs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.vs.spv new file mode 100644 index 0000000000..6f2816fd49 Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Line.vs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/LineLine.fs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/LineLine.fs.md5 new file mode 100644 index 0000000000..be39e053be --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/LineLine.fs.md5 @@ -0,0 +1 @@ +`Óê¼}oJkꓱB \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/LineLine.fs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/LineLine.fs.spv new file mode 100644 index 0000000000..a8a6b38aac Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/LineLine.fs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/LineLine.gs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/LineLine.gs.md5 new file mode 100644 index 0000000000..ccf172d323 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/LineLine.gs.md5 @@ -0,0 +1 @@ +ló]_0„^¢…EY.NF´° \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Plane.fs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Plane.fs.spv new file mode 100644 index 0000000000..ef303b5d6a Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Plane.fs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Plane.gs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Plane.gs.md5 new file mode 100644 index 0000000000..fb07f68b04 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Plane.gs.md5 @@ -0,0 +1 @@ +XÙõ?HA+`ú„SÃSfH \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Plane.gs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Plane.gs.spv new file mode 100644 index 0000000000..6305e98537 Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Plane.gs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Plane.vs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Plane.vs.md5 new file mode 100644 index 0000000000..b5e9b52d7f --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Plane.vs.md5 @@ -0,0 +1 @@ +÷/×4‘Jõ¼T ]¿ëö° \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Plane.vs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Plane.vs.spv new file mode 100644 index 0000000000..8abce988d1 Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/Plane.vs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.fs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.fs.md5 new file mode 100644 index 0000000000..7029f4e7ee --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.fs.md5 @@ -0,0 +1 @@ +÷<Öæ¹\&-ÀŒ¶¸ÏBîq \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.fs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.fs.spv new file mode 100644 index 0000000000..b64dfe2d36 Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.fs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.gs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.gs.md5 new file mode 100644 index 0000000000..3bd88ae410 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.gs.md5 @@ -0,0 +1 @@ +Zr4)ñ…´$ãÎí?e6Lé \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.gs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.gs.spv new file mode 100644 index 0000000000..56e512a689 Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.gs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.vs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.vs.md5 new file mode 100644 index 0000000000..8f25d5766d --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.vs.md5 @@ -0,0 +1 @@ +FÝņ¢qU‡ÎN‡á? \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.vs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.vs.spv new file mode 100644 index 0000000000..e280a88e0f Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/SimpleIcon.vs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.fs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.fs.md5 new file mode 100644 index 0000000000..2d14bd281e --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.fs.md5 @@ -0,0 +1 @@ +oÁÖCˇ½r¬ÒdÞi’•ª \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.fs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.fs.spv new file mode 100644 index 0000000000..63da16d727 Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.fs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.gs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.gs.md5 new file mode 100644 index 0000000000..ebf7d09811 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.gs.md5 @@ -0,0 +1 @@ +¼¸ø{ãl$6åÙu–_ \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.gs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.gs.spv new file mode 100644 index 0000000000..a3a6c8a107 Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.gs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.vs.md5 b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.vs.md5 new file mode 100644 index 0000000000..4f67df951a --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.vs.md5 @@ -0,0 +1 @@ +|/*~w ‹ÔNù„æu \ No newline at end of file diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.vs.spv b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.vs.spv new file mode 100644 index 0000000000..c970d45603 Binary files /dev/null and b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/shaders/compiled/VertexIcon.vs.spv differ diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/utils/CVKFormatUtils.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/utils/CVKFormatUtils.java new file mode 100644 index 0000000000..531efc62af --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/utils/CVKFormatUtils.java @@ -0,0 +1,497 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.utils; + +import static org.lwjgl.vulkan.IMGFormatPVRTC.VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG; +import static org.lwjgl.vulkan.IMGFormatPVRTC.VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG; +import static org.lwjgl.vulkan.IMGFormatPVRTC.VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG; +import static org.lwjgl.vulkan.IMGFormatPVRTC.VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG; +import static org.lwjgl.vulkan.IMGFormatPVRTC.VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG; +import static org.lwjgl.vulkan.IMGFormatPVRTC.VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG; +import static org.lwjgl.vulkan.IMGFormatPVRTC.VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG; +import static org.lwjgl.vulkan.IMGFormatPVRTC.VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A1R5G5B5_UNORM_PACK16; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A2B10G10R10_SINT_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A2B10G10R10_SNORM_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A2B10G10R10_SSCALED_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A2B10G10R10_UINT_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A2B10G10R10_UNORM_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A2B10G10R10_USCALED_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A2R10G10B10_SINT_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A2R10G10B10_SNORM_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A2R10G10B10_SSCALED_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A2R10G10B10_UINT_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A2R10G10B10_UNORM_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A2R10G10B10_USCALED_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A8B8G8R8_SINT_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A8B8G8R8_SNORM_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A8B8G8R8_SRGB_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A8B8G8R8_SSCALED_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A8B8G8R8_UINT_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A8B8G8R8_UNORM_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_A8B8G8R8_USCALED_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_10x10_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_10x10_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_10x5_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_10x5_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_10x6_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_10x6_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_10x8_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_10x8_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_12x10_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_12x10_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_12x12_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_12x12_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_4x4_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_4x4_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_5x4_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_5x4_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_5x5_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_5x5_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_6x5_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_6x5_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_6x6_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_6x6_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_8x5_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_8x5_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_8x6_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_8x6_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_8x8_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ASTC_8x8_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B10G11R11_UFLOAT_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B4G4R4A4_UNORM_PACK16; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B5G5R5A1_UNORM_PACK16; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B5G6R5_UNORM_PACK16; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B8G8R8A8_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B8G8R8A8_SNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B8G8R8A8_SRGB; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B8G8R8A8_SSCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B8G8R8A8_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B8G8R8A8_UNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B8G8R8A8_USCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B8G8R8_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B8G8R8_SNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B8G8R8_SRGB; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B8G8R8_SSCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B8G8R8_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B8G8R8_UNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_B8G8R8_USCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_BC1_RGBA_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_BC1_RGBA_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_BC1_RGB_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_BC1_RGB_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_BC2_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_BC2_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_BC3_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_BC3_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_BC4_SNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_BC4_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_BC5_SNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_BC5_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_BC6H_SFLOAT_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_BC6H_UFLOAT_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_BC7_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_BC7_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_D16_UNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_D16_UNORM_S8_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_D24_UNORM_S8_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_D32_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_D32_SFLOAT_S8_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_E5B9G9R9_UFLOAT_PACK32; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_EAC_R11G11_SNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_EAC_R11G11_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_EAC_R11_SNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_EAC_R11_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16B16A16_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16B16A16_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16B16A16_SNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16B16A16_SSCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16B16A16_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16B16A16_UNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16B16A16_USCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16B16_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16B16_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16B16_SNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16B16_SSCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16B16_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16B16_UNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16B16_USCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16_SNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16_SSCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16_UNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16G16_USCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16_SNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16_SSCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16_UNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R16_USCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32B32A32_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32B32A32_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32B32A32_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32B32_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32B32_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32B32_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32G32_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R32_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R4G4B4A4_UNORM_PACK16; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R4G4_UNORM_PACK8; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R5G5B5A1_UNORM_PACK16; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R5G6B5_UNORM_PACK16; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R64G64B64A64_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R64G64B64A64_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R64G64B64A64_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R64G64B64_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R64G64B64_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R64G64B64_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R64G64_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R64G64_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R64G64_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R64_SFLOAT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R64_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R64_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8A8_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8A8_SNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8A8_SRGB; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8A8_SSCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8A8_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8A8_UNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8A8_USCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8_SNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8_SRGB; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8_SSCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8_UNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8B8_USCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8_SNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8_SRGB; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8_SSCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8_UNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8G8_USCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8_SINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8_SNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8_SRGB; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8_SSCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8_UNORM; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_R8_USCALED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_S8_UINT; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_UNDEFINED; +import static org.lwjgl.vulkan.VK10.VK_FORMAT_X8_D24_UNORM_PACK32; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_B16G16R16G16_422_UNORM; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_B8G8R8G8_422_UNORM; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G16B16G16R16_422_UNORM; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G16_B16R16_2PLANE_420_UNORM; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G16_B16R16_2PLANE_422_UNORM; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G8B8G8R8_422_UNORM; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G8_B8R8_2PLANE_420_UNORM; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G8_B8R8_2PLANE_422_UNORM; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_R10X6G10X6_UNORM_2PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_R10X6_UNORM_PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_R12X4G12X4_UNORM_2PACK16; +import static org.lwjgl.vulkan.VK11.VK_FORMAT_R12X4_UNORM_PACK16; + +public class CVKFormatUtils { +public static boolean VkFormatHasDepthComponent(int format) { + return format == VK_FORMAT_D16_UNORM || + format == VK_FORMAT_X8_D24_UNORM_PACK32 || + format == VK_FORMAT_D32_SFLOAT || + format == VK_FORMAT_D16_UNORM_S8_UINT || + format == VK_FORMAT_D24_UNORM_S8_UINT || + format == VK_FORMAT_D32_SFLOAT_S8_UINT; + } + + public static boolean VkFormatHasStencilComponent(int format) { + return format == VK_FORMAT_S8_UINT || + format == VK_FORMAT_D16_UNORM_S8_UINT || + format == VK_FORMAT_D24_UNORM_S8_UINT || + format == VK_FORMAT_D32_SFLOAT_S8_UINT; + } + + public static int VkFormatByteDepth(int format) { + switch (format) { + case VK_FORMAT_UNDEFINED: return 0; + case VK_FORMAT_R4G4_UNORM_PACK8: return 1; + case VK_FORMAT_R4G4B4A4_UNORM_PACK16: return 2; + case VK_FORMAT_B4G4R4A4_UNORM_PACK16: return 2; + case VK_FORMAT_R5G6B5_UNORM_PACK16: return 2; + case VK_FORMAT_B5G6R5_UNORM_PACK16: return 2; + case VK_FORMAT_R5G5B5A1_UNORM_PACK16: return 2; + case VK_FORMAT_B5G5R5A1_UNORM_PACK16: return 2; + case VK_FORMAT_A1R5G5B5_UNORM_PACK16: return 2; + case VK_FORMAT_R8_UNORM: return 1; + case VK_FORMAT_R8_SNORM: return 1; + case VK_FORMAT_R8_USCALED: return 1; + case VK_FORMAT_R8_SSCALED: return 1; + case VK_FORMAT_R8_UINT: return 1; + case VK_FORMAT_R8_SINT: return 1; + case VK_FORMAT_R8_SRGB: return 1; + case VK_FORMAT_R8G8_UNORM: return 2; + case VK_FORMAT_R8G8_SNORM: return 2; + case VK_FORMAT_R8G8_USCALED: return 2; + case VK_FORMAT_R8G8_SSCALED: return 2; + case VK_FORMAT_R8G8_UINT: return 2; + case VK_FORMAT_R8G8_SINT: return 2; + case VK_FORMAT_R8G8_SRGB: return 2; + case VK_FORMAT_R8G8B8_UNORM: return 3; + case VK_FORMAT_R8G8B8_SNORM: return 3; + case VK_FORMAT_R8G8B8_USCALED: return 3; + case VK_FORMAT_R8G8B8_SSCALED: return 3; + case VK_FORMAT_R8G8B8_UINT: return 3; + case VK_FORMAT_R8G8B8_SINT: return 3; + case VK_FORMAT_R8G8B8_SRGB: return 3; + case VK_FORMAT_B8G8R8_UNORM: return 3; + case VK_FORMAT_B8G8R8_SNORM: return 3; + case VK_FORMAT_B8G8R8_USCALED: return 3; + case VK_FORMAT_B8G8R8_SSCALED: return 3; + case VK_FORMAT_B8G8R8_UINT: return 3; + case VK_FORMAT_B8G8R8_SINT: return 3; + case VK_FORMAT_B8G8R8_SRGB: return 3; + case VK_FORMAT_R8G8B8A8_UNORM: return 4; + case VK_FORMAT_R8G8B8A8_SNORM: return 4; + case VK_FORMAT_R8G8B8A8_USCALED: return 4; + case VK_FORMAT_R8G8B8A8_SSCALED: return 4; + case VK_FORMAT_R8G8B8A8_UINT: return 4; + case VK_FORMAT_R8G8B8A8_SINT: return 4; + case VK_FORMAT_R8G8B8A8_SRGB: return 4; + case VK_FORMAT_B8G8R8A8_UNORM: return 4; + case VK_FORMAT_B8G8R8A8_SNORM: return 4; + case VK_FORMAT_B8G8R8A8_USCALED: return 4; + case VK_FORMAT_B8G8R8A8_SSCALED: return 4; + case VK_FORMAT_B8G8R8A8_UINT: return 4; + case VK_FORMAT_B8G8R8A8_SINT: return 4; + case VK_FORMAT_B8G8R8A8_SRGB: return 4; + case VK_FORMAT_A8B8G8R8_UNORM_PACK32: return 4; + case VK_FORMAT_A8B8G8R8_SNORM_PACK32: return 4; + case VK_FORMAT_A8B8G8R8_USCALED_PACK32: return 4; + case VK_FORMAT_A8B8G8R8_SSCALED_PACK32: return 4; + case VK_FORMAT_A8B8G8R8_UINT_PACK32: return 4; + case VK_FORMAT_A8B8G8R8_SINT_PACK32: return 4; + case VK_FORMAT_A8B8G8R8_SRGB_PACK32: return 4; + case VK_FORMAT_A2R10G10B10_UNORM_PACK32: return 4; + case VK_FORMAT_A2R10G10B10_SNORM_PACK32: return 4; + case VK_FORMAT_A2R10G10B10_USCALED_PACK32: return 4; + case VK_FORMAT_A2R10G10B10_SSCALED_PACK32: return 4; + case VK_FORMAT_A2R10G10B10_UINT_PACK32: return 4; + case VK_FORMAT_A2R10G10B10_SINT_PACK32: return 4; + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return 4; + case VK_FORMAT_A2B10G10R10_SNORM_PACK32: return 4; + case VK_FORMAT_A2B10G10R10_USCALED_PACK32: return 4; + case VK_FORMAT_A2B10G10R10_SSCALED_PACK32: return 4; + case VK_FORMAT_A2B10G10R10_UINT_PACK32: return 4; + case VK_FORMAT_A2B10G10R10_SINT_PACK32: return 4; + case VK_FORMAT_R16_UNORM: return 2; + case VK_FORMAT_R16_SNORM: return 2; + case VK_FORMAT_R16_USCALED: return 2; + case VK_FORMAT_R16_SSCALED: return 2; + case VK_FORMAT_R16_UINT: return 2; + case VK_FORMAT_R16_SINT: return 2; + case VK_FORMAT_R16_SFLOAT: return 2; + case VK_FORMAT_R16G16_UNORM: return 4; + case VK_FORMAT_R16G16_SNORM: return 4; + case VK_FORMAT_R16G16_USCALED: return 4; + case VK_FORMAT_R16G16_SSCALED: return 4; + case VK_FORMAT_R16G16_UINT: return 4; + case VK_FORMAT_R16G16_SINT: return 4; + case VK_FORMAT_R16G16_SFLOAT: return 4; + case VK_FORMAT_R16G16B16_UNORM: return 6; + case VK_FORMAT_R16G16B16_SNORM: return 6; + case VK_FORMAT_R16G16B16_USCALED: return 6; + case VK_FORMAT_R16G16B16_SSCALED: return 6; + case VK_FORMAT_R16G16B16_UINT: return 6; + case VK_FORMAT_R16G16B16_SINT: return 6; + case VK_FORMAT_R16G16B16_SFLOAT: return 6; + case VK_FORMAT_R16G16B16A16_UNORM: return 8; + case VK_FORMAT_R16G16B16A16_SNORM: return 8; + case VK_FORMAT_R16G16B16A16_USCALED: return 8; + case VK_FORMAT_R16G16B16A16_SSCALED: return 8; + case VK_FORMAT_R16G16B16A16_UINT: return 8; + case VK_FORMAT_R16G16B16A16_SINT: return 8; + case VK_FORMAT_R16G16B16A16_SFLOAT: return 8; + case VK_FORMAT_R32_UINT: return 4; + case VK_FORMAT_R32_SINT: return 4; + case VK_FORMAT_R32_SFLOAT: return 4; + case VK_FORMAT_R32G32_UINT: return 8; + case VK_FORMAT_R32G32_SINT: return 8; + case VK_FORMAT_R32G32_SFLOAT: return 8; + case VK_FORMAT_R32G32B32_UINT: return 12; + case VK_FORMAT_R32G32B32_SINT: return 12; + case VK_FORMAT_R32G32B32_SFLOAT: return 12; + case VK_FORMAT_R32G32B32A32_UINT: return 16; + case VK_FORMAT_R32G32B32A32_SINT: return 16; + case VK_FORMAT_R32G32B32A32_SFLOAT: return 16; + case VK_FORMAT_R64_UINT: return 8; + case VK_FORMAT_R64_SINT: return 8; + case VK_FORMAT_R64_SFLOAT: return 8; + case VK_FORMAT_R64G64_UINT: return 16; + case VK_FORMAT_R64G64_SINT: return 16; + case VK_FORMAT_R64G64_SFLOAT: return 16; + case VK_FORMAT_R64G64B64_UINT: return 24; + case VK_FORMAT_R64G64B64_SINT: return 24; + case VK_FORMAT_R64G64B64_SFLOAT: return 24; + case VK_FORMAT_R64G64B64A64_UINT: return 32; + case VK_FORMAT_R64G64B64A64_SINT: return 32; + case VK_FORMAT_R64G64B64A64_SFLOAT: return 32; + case VK_FORMAT_B10G11R11_UFLOAT_PACK32: return 4; + case VK_FORMAT_E5B9G9R9_UFLOAT_PACK32: return 4; + case VK_FORMAT_D16_UNORM: return 2; + case VK_FORMAT_X8_D24_UNORM_PACK32: return 4; + case VK_FORMAT_D32_SFLOAT: return 4; + case VK_FORMAT_S8_UINT: return 1; + case VK_FORMAT_D16_UNORM_S8_UINT: return 3; + case VK_FORMAT_D24_UNORM_S8_UINT: return 4; + case VK_FORMAT_D32_SFLOAT_S8_UINT: return 8; + case VK_FORMAT_BC1_RGB_UNORM_BLOCK: return 8; + case VK_FORMAT_BC1_RGB_SRGB_BLOCK: return 8; + case VK_FORMAT_BC1_RGBA_UNORM_BLOCK: return 8; + case VK_FORMAT_BC1_RGBA_SRGB_BLOCK: return 8; + case VK_FORMAT_BC2_UNORM_BLOCK: return 16; + case VK_FORMAT_BC2_SRGB_BLOCK: return 16; + case VK_FORMAT_BC3_UNORM_BLOCK: return 16; + case VK_FORMAT_BC3_SRGB_BLOCK: return 16; + case VK_FORMAT_BC4_UNORM_BLOCK: return 8; + case VK_FORMAT_BC4_SNORM_BLOCK: return 8; + case VK_FORMAT_BC5_UNORM_BLOCK: return 16; + case VK_FORMAT_BC5_SNORM_BLOCK: return 16; + case VK_FORMAT_BC6H_UFLOAT_BLOCK: return 16; + case VK_FORMAT_BC6H_SFLOAT_BLOCK: return 16; + case VK_FORMAT_BC7_UNORM_BLOCK: return 16; + case VK_FORMAT_BC7_SRGB_BLOCK: return 16; + case VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK: return 8; + case VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK: return 8; + case VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK: return 8; + case VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK: return 8; + case VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK: return 16; + case VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK: return 16; + case VK_FORMAT_EAC_R11_UNORM_BLOCK: return 8; + case VK_FORMAT_EAC_R11_SNORM_BLOCK: return 8; + case VK_FORMAT_EAC_R11G11_UNORM_BLOCK: return 16; + case VK_FORMAT_EAC_R11G11_SNORM_BLOCK: return 16; + case VK_FORMAT_ASTC_4x4_UNORM_BLOCK: return 16; + case VK_FORMAT_ASTC_4x4_SRGB_BLOCK: return 16; + case VK_FORMAT_ASTC_5x4_UNORM_BLOCK: return 16; + case VK_FORMAT_ASTC_5x4_SRGB_BLOCK: return 16; + case VK_FORMAT_ASTC_5x5_UNORM_BLOCK: return 16; + case VK_FORMAT_ASTC_5x5_SRGB_BLOCK: return 16; + case VK_FORMAT_ASTC_6x5_UNORM_BLOCK: return 16; + case VK_FORMAT_ASTC_6x5_SRGB_BLOCK: return 16; + case VK_FORMAT_ASTC_6x6_UNORM_BLOCK: return 16; + case VK_FORMAT_ASTC_6x6_SRGB_BLOCK: return 16; + case VK_FORMAT_ASTC_8x5_UNORM_BLOCK: return 16; + case VK_FORMAT_ASTC_8x5_SRGB_BLOCK: return 16; + case VK_FORMAT_ASTC_8x6_UNORM_BLOCK: return 16; + case VK_FORMAT_ASTC_8x6_SRGB_BLOCK: return 16; + case VK_FORMAT_ASTC_8x8_UNORM_BLOCK: return 16; + case VK_FORMAT_ASTC_8x8_SRGB_BLOCK: return 16; + case VK_FORMAT_ASTC_10x5_UNORM_BLOCK: return 16; + case VK_FORMAT_ASTC_10x5_SRGB_BLOCK: return 16; + case VK_FORMAT_ASTC_10x6_UNORM_BLOCK: return 16; + case VK_FORMAT_ASTC_10x6_SRGB_BLOCK: return 16; + case VK_FORMAT_ASTC_10x8_UNORM_BLOCK: return 16; + case VK_FORMAT_ASTC_10x8_SRGB_BLOCK: return 16; + case VK_FORMAT_ASTC_10x10_UNORM_BLOCK: return 16; + case VK_FORMAT_ASTC_10x10_SRGB_BLOCK: return 16; + case VK_FORMAT_ASTC_12x10_UNORM_BLOCK: return 16; + case VK_FORMAT_ASTC_12x10_SRGB_BLOCK: return 16; + case VK_FORMAT_ASTC_12x12_UNORM_BLOCK: return 16; + case VK_FORMAT_ASTC_12x12_SRGB_BLOCK: return 16; + case VK_FORMAT_PVRTC1_2BPP_UNORM_BLOCK_IMG: return 8; + case VK_FORMAT_PVRTC1_4BPP_UNORM_BLOCK_IMG: return 8; + case VK_FORMAT_PVRTC2_2BPP_UNORM_BLOCK_IMG: return 8; + case VK_FORMAT_PVRTC2_4BPP_UNORM_BLOCK_IMG: return 8; + case VK_FORMAT_PVRTC1_2BPP_SRGB_BLOCK_IMG: return 8; + case VK_FORMAT_PVRTC1_4BPP_SRGB_BLOCK_IMG: return 8; + case VK_FORMAT_PVRTC2_2BPP_SRGB_BLOCK_IMG: return 8; + case VK_FORMAT_PVRTC2_4BPP_SRGB_BLOCK_IMG: return 8; + case VK_FORMAT_R10X6_UNORM_PACK16: return 2; + case VK_FORMAT_R10X6G10X6_UNORM_2PACK16: return 4; + case VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16: return 8; + case VK_FORMAT_R12X4_UNORM_PACK16: return 2; + case VK_FORMAT_R12X4G12X4_UNORM_2PACK16: return 4; + case VK_FORMAT_R12X4G12X4B12X4A12X4_UNORM_4PACK16: return 8; + case VK_FORMAT_G8B8G8R8_422_UNORM: return 4; + case VK_FORMAT_B8G8R8G8_422_UNORM: return 4; + case VK_FORMAT_G10X6B10X6G10X6R10X6_422_UNORM_4PACK16: return 8; + case VK_FORMAT_B10X6G10X6R10X6G10X6_422_UNORM_4PACK16: return 8; + case VK_FORMAT_G12X4B12X4G12X4R12X4_422_UNORM_4PACK16: return 8; + case VK_FORMAT_B12X4G12X4R12X4G12X4_422_UNORM_4PACK16: return 8; + case VK_FORMAT_G16B16G16R16_422_UNORM: return 8; + case VK_FORMAT_B16G16R16G16_422_UNORM: return 8; + case VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM: return 6; + case VK_FORMAT_G8_B8R8_2PLANE_420_UNORM: return 6; + case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_420_UNORM_3PACK16: return 12; + case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16: return 12; + case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_420_UNORM_3PACK16: return 12; + case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_420_UNORM_3PACK16: return 12; + case VK_FORMAT_G16_B16_R16_3PLANE_420_UNORM: return 12; + case VK_FORMAT_G16_B16R16_2PLANE_420_UNORM: return 12; + case VK_FORMAT_G8_B8_R8_3PLANE_422_UNORM: return 4; + case VK_FORMAT_G8_B8R8_2PLANE_422_UNORM: return 4; + case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_422_UNORM_3PACK16: return 8; + case VK_FORMAT_G10X6_B10X6R10X6_2PLANE_422_UNORM_3PACK16: return 8; + case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_422_UNORM_3PACK16: return 8; + case VK_FORMAT_G12X4_B12X4R12X4_2PLANE_422_UNORM_3PACK16: return 8; + case VK_FORMAT_G16_B16_R16_3PLANE_422_UNORM: return 8; + case VK_FORMAT_G16_B16R16_2PLANE_422_UNORM: return 8; + case VK_FORMAT_G8_B8_R8_3PLANE_444_UNORM: return 3; + case VK_FORMAT_G10X6_B10X6_R10X6_3PLANE_444_UNORM_3PACK16: return 6; + case VK_FORMAT_G12X4_B12X4_R12X4_3PLANE_444_UNORM_3PACK16: return 6; + case VK_FORMAT_G16_B16_R16_3PLANE_444_UNORM: return 6; + + default: + throw new RuntimeException(String.format("UNKNOWN FORMAT: %d", format)); + } + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/utils/CVKGraphLogger.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/utils/CVKGraphLogger.java new file mode 100644 index 0000000000..3349864f55 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/utils/CVKGraphLogger.java @@ -0,0 +1,373 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.utils; + +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_DEBUGGING; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.logging.FileHandler; +import java.util.logging.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; +import java.util.logging.StreamHandler; +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.CVK_DEFAULT_LOG_LEVEL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import org.openide.modules.Places; + +public class CVKGraphLogger { + // Static logger used by static code like shader loading or by code that doesn't know + // what graph it is working for. + private final static Logger CVKLOGGER = CreateNamedFileLogger("CVK", CVK_DEFAULT_LOG_LEVEL); + + // Variables for the per graph loggers + private static final boolean INDIVIDUAL_LOGS = true; + private static int NEXT_LOGGER_ID = 0; + private static CVKGraphLogger staticLogger = null; + private final int loggerId; + private int indentation = 0; + private final Logger graphLogger; + + + // ========================> Classes <======================== \\ + + public static class CVKGraphLogRecord extends LogRecord { + public final int loggerId; + public final int indentation; + public final boolean formatted; + public boolean graphAnnotation; + + public CVKGraphLogRecord(Level level, String msg, final int loggerId) { + this(level, msg, loggerId, 0, true, true); + } + public CVKGraphLogRecord(Level level, String msg, final int loggerId, final int indentation) { + this(level, msg, loggerId, indentation, true, true); + } + public CVKGraphLogRecord(Level level, String msg, final int loggerId, final int indentation, boolean formatted, boolean graphAnnotation) { + super(level, msg); + this.loggerId = loggerId; + this.indentation = indentation; + this.formatted = formatted; + this.graphAnnotation = graphAnnotation; + } + } + + public static class CVKGraphLogFormatter extends Formatter { + public static int indent = 0; + public final static int PADLEN = 40; + + @Override + public String format(LogRecord record) { + // This does all the VA_ARGS formatting + String msg = formatMessage(record); + + // Don't fuss about with line and file for empty lines + if (msg.isBlank()) { + return msg; + } + + final StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + final StringBuilder lineBuilder = new StringBuilder(); + final int indentation; + int stackLevel = 8; // A guesstimation based on a few observations + final boolean formatting; + final boolean graphAnnotation; + + if (record instanceof CVKGraphLogRecord) { + CVKGraphLogRecord graphRecord = (CVKGraphLogRecord)record; + indentation = graphRecord.indentation; + formatting = graphRecord.formatted; + graphAnnotation = graphRecord.graphAnnotation; + if (graphAnnotation) { + lineBuilder.append(String.format("[graph %d] ", graphRecord.loggerId)); + } + + // Find the first call before CVKGraphLogger + int level = 0; + boolean encounteredCVKGraphLogger = false; + for (var el : stackTrace) { + if (el.getClassName().endsWith("CVKGraphLogger")) { + encounteredCVKGraphLogger = true; + } else if (encounteredCVKGraphLogger) { + stackLevel = level; + break; + } + level++; + } + } else { + indentation = indent; + formatting = true; + graphAnnotation = true; + + // Find the first constellation call (after this log format method) + int level = 0; + boolean encounteredConstellation = false; + for (var el : stackTrace) { + final String className = el.getClassName(); + if (className.contains(".constellation.") && !className.endsWith("CVKGraphLogFormatter")) { + stackLevel = level; + break; + } + ++level; + } + } + + if (formatting) { + // With a grand total of 1 observation, the call we are interested + // is at element 8 + if (stackTrace.length >= stackLevel) { + StackTraceElement ste = stackTrace[stackLevel]; + String fileAndLine = String.format("%-7s TID:%d %s:%d>", + record.getLevel() != null ? record.getLevel().toString() : "null", + record.getThreadID(), + ste.getFileName(), + ste.getLineNumber()); + lineBuilder.append(fileAndLine); + + // Right pad (additional to indent padding) up to PADLEN so we have + // table like output for records at the same indent level + int padding = PADLEN - fileAndLine.length(); + for (int i = 0; i < padding; ++i) { + lineBuilder.append(" "); + } + } + + // StartLogSection increments indent + for (int i = 0; i < indentation; ++i) { + lineBuilder.append(" "); + } + + if (record.getLevel() == Level.WARNING) { + lineBuilder.append("!WARNING! "); + } + } + + StringBuilder stringBuilder = new StringBuilder(); + + // Really make SEVERE stand out + if (formatting && record.getLevel() == Level.SEVERE) { + stringBuilder.append("\r\n======================================================= SEVERE =======================================================\n"); + } + + String msgLines[] = msg.split("\\r?\\n"); + for (String msgLine : msgLines) { + if (!msgLine.isBlank()) { + stringBuilder.append(lineBuilder.toString()); + stringBuilder.append(msgLine); + } + stringBuilder.append(System.getProperty("line.separator")); + } + + // Really make SEVERE stand out + if (formatting && record.getLevel() == Level.SEVERE) { + stringBuilder.append("======================================================================================================================\r\n\r\n"); + } + + return stringBuilder.toString(); + } + } + + + // ========================> Static Logging <======================== \\ + + public static Logger CreateNamedFileLogger(String name, Level level) { + Logger logger = Logger.getLogger(name); + logger.setUseParentHandlers(false); + logger.setLevel(level); + + // Delete old log + final String logName = String.format("%s.log", name); + Path logPath = Paths.get(Places.getUserDirectory().getPath(), "../..", logName).toAbsolutePath().normalize(); + + try { + logPath.toFile().delete(); + } + catch(Exception e) { + // old log doesn't exist or is locked, oh well keep going + } + + try { + FileHandler fileHandler = new FileHandler(logPath.toString()); + fileHandler.setFormatter(new CVKGraphLogFormatter()); + logger.addHandler(fileHandler); + } catch (IOException e) { + logger.log(Level.WARNING, "Logger failed to create {0}, exception: {1}", + new Object[]{logName, e.toString()}); + } + + StreamHandler streamHanlder = new StreamHandler(System.out, new CVKGraphLogFormatter()); + logger.addHandler(streamHanlder); + + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss dd/MM/yyyy"); + LocalDateTime now = LocalDateTime.now(); + + String logHeader = String.format("Log created %s\r\nCVK_DEBUGGING: %s\r\nLogger level: %s\r\n\r\n", + dateTimeFormatter.format(now), + CVK_DEBUGGING ? "true" : "false", + CVK_DEFAULT_LOG_LEVEL.toString()); + CVKGraphLogRecord record = new CVKGraphLogRecord(level, logHeader, 0, 0, false, false); + logger.log(record); + + return logger; + } + + public static CVKGraphLogger GetStaticLogger() { + if (staticLogger == null) { + staticLogger = new CVKGraphLogger(); + } + return staticLogger; + } + + + // ========================> Per Graph Logging <======================== \\ + + private CVKGraphLogger() { + graphLogger = null; + loggerId = -1; + } + + public CVKGraphLogger(String graphId) { + this.loggerId = NEXT_LOGGER_ID++; + if (INDIVIDUAL_LOGS) { + graphLogger = CreateNamedFileLogger(String.format("CVK_graph_%d", loggerId), CVK_DEFAULT_LOG_LEVEL); + } else { + graphLogger = null; + } + + DoLog(Level.SEVERE, String.format("Graph %s using logger %d", graphId, loggerId), loggerId, 0, false); + } + + public boolean isLoggable(Level level) { + return graphLogger != null ? graphLogger.isLoggable(level) : CVKLOGGER.isLoggable(level); + } + + private void DoLog(Level level, String msg) { + DoLog(level, msg, loggerId, indentation, true); + } + + private void DoLog(Level level, String msg, final int loggerId, final int indentation, boolean formatted) { + CVKGraphLogRecord record = new CVKGraphLogRecord(level, msg, loggerId, indentation, formatted, false); + if (graphLogger != null) { + graphLogger.log(record); + record.graphAnnotation = true; + } + CVKLOGGER.log(record); + } + + public void log(Level level, String format, Object... args) { + if (CVKLOGGER.isLoggable(level)) { + String msg = String.format(format, args); + DoLog(level, msg); + } + } + + public void log(Level level, String msg) { + if (CVKLOGGER.isLoggable(level)) { + DoLog(level, msg); + } + } + + public void severe(String msg) { + log(Level.SEVERE, msg); + } + public void severe(String format, Object... args) { + log(Level.SEVERE, format, args); + } + public void warning(String msg) { + log(Level.WARNING, msg); + } + public void warning(String format, Object... args) { + log(Level.WARNING, format, args); + } + public void info(String msg) { + log(Level.INFO, msg); + } + public void info(String format, Object... args) { + log(Level.INFO, format, args); + } + public void config(String msg) { + log(Level.INFO, msg); + } + public void config(String format, Object... args) { + log(Level.INFO, format, args); + } + public void fine(String msg) { + log(Level.FINE, msg); + } + public void fine(String format, Object... args) { + log(Level.FINE, format, args); + } + public void finer(String msg) { + log(Level.FINER, msg); + } + public void finer(String format, Object... args) { + log(Level.FINER, format, args); + } + public void finest(String msg) { + log(Level.FINEST, msg); + } + public void finest(String format, Object... args) { + log(Level.FINEST, format, args); + } + + public void StartLogSection(String msg) { + StartLogSection(Level.INFO, msg); + } + + public void StartLogSection(Level level, String msg) { + if (CVKLOGGER.isLoggable(level)) { + log(level, "%s---- START %s ----", System.getProperty("line.separator"), msg); + ++indentation; + } + } + + public void EndLogSection(String msg) { + EndLogSection(Level.INFO, msg); + } + + public void EndLogSection(Level level, String msg) { + if (CVKLOGGER.isLoggable(level)) { + indentation = Math.max(0, indentation - 1); + log(level, "---- END %s ----", msg); + log(level, System.getProperty("line.separator")); + } + } + + public void LogException(Exception exception, String format, Object... args) { + String msg = String.format(format, args); + LogException(exception, msg); + } + + public void LogException(Exception exception, String msg) { + final StringWriter exceptionTraceWriter = new StringWriter(); + final PrintWriter exceptionPrintWriter = new PrintWriter( exceptionTraceWriter ); + exception.printStackTrace(exceptionPrintWriter); + exceptionPrintWriter.flush(); + severe("\r\n%s\r\n%s", msg, exceptionTraceWriter.toString()); + } + + public void Flush() { + Logger logger = graphLogger != null ? graphLogger : CVKLOGGER; + + for (var handler : logger.getHandlers()) { + handler.flush(); + } + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/utils/CVKMissingEnums.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/utils/CVKMissingEnums.java new file mode 100644 index 0000000000..4684f88ffe --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/utils/CVKMissingEnums.java @@ -0,0 +1,454 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.utils; + + + +/** + * This class contains enums that represent enums in the C Vulkan API that + * are either missing in LWJGL or are represented by multiple static finals. + * By mirroring these 'missing' enums we can utilise enums string reprentation + * in Java. In some instances they also make the code easier to follow. + * + * - HERE BE DRAGONS - + * The order of these enums and any explicit values assigned to them matches the + * Vulkan API. Do not reorder them, do not add entries unless at the tail, do not + * remove entries and do not change explicit values. + * + */ +public class CVKMissingEnums { + /* + * Some enums are are sequential, some are assigned explicit values, this + * method ensures we always get a value that is compatible with the native + * Vulkan API + */ + private interface IntValue { + public abstract int Value(); + } + + + /** + * + */ + public enum VkColorSpaceKHR implements IntValue { + VK_COLOR_SPACE_SRGB_NONLINEAR_KHR(0), + VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT(1000104001), + VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT(1000104002), + VK_COLOR_SPACE_DISPLAY_P3_LINEAR_EXT(1000104003), + VK_COLOR_SPACE_DCI_P3_NONLINEAR_EXT(1000104004), + VK_COLOR_SPACE_BT709_LINEAR_EXT(1000104005), + VK_COLOR_SPACE_BT709_NONLINEAR_EXT(1000104006), + VK_COLOR_SPACE_BT2020_LINEAR_EXT(1000104007), + VK_COLOR_SPACE_HDR10_ST2084_EXT(1000104008), + VK_COLOR_SPACE_DOLBYVISION_EXT(1000104009), + VK_COLOR_SPACE_HDR10_HLG_EXT(1000104010), + VK_COLOR_SPACE_ADOBERGB_LINEAR_EXT(1000104011), + VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT(1000104012), + VK_COLOR_SPACE_PASS_THROUGH_EXT(1000104013), + VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT(1000104014), + VK_COLOR_SPACE_DISPLAY_NATIVE_AMD(1000213000), + VK_COLOR_SPACE_DCI_P3_LINEAR_EXT(1000104003), + + // Added sentinal value + VK_COLOR_SPACE_NONE(-1); + + private final int val; + private VkColorSpaceKHR(int v) { val = v; } + public static VkColorSpaceKHR GetByValue(int value) { + for (VkColorSpaceKHR e : VkColorSpaceKHR.values()) { + if (value == e.val) { + return e; + } + } + return VK_COLOR_SPACE_NONE; + } + @Override + public int Value() { return val; } + } + + /** + * This is a list of constant ints in VK10.java but having it as enum simplifies + * translating values to string in Java. + */ + public enum VkFormat implements IntValue { + VK_FORMAT_UNDEFINED, + VK_FORMAT_R4G4_UNORM_PACK8, + VK_FORMAT_R4G4B4A4_UNORM_PACK16, + VK_FORMAT_B4G4R4A4_UNORM_PACK16, + VK_FORMAT_R5G6B5_UNORM_PACK16, + VK_FORMAT_B5G6R5_UNORM_PACK16, + VK_FORMAT_R5G5B5A1_UNORM_PACK16, + VK_FORMAT_B5G5R5A1_UNORM_PACK16, + VK_FORMAT_A1R5G5B5_UNORM_PACK16, + VK_FORMAT_R8_UNORM, + VK_FORMAT_R8_SNORM, + VK_FORMAT_R8_USCALED, + VK_FORMAT_R8_SSCALED, + VK_FORMAT_R8_UINT, + VK_FORMAT_R8_SINT, + VK_FORMAT_R8_SRGB, + VK_FORMAT_R8G8_UNORM, + VK_FORMAT_R8G8_SNORM, + VK_FORMAT_R8G8_USCALED, + VK_FORMAT_R8G8_SSCALED, + VK_FORMAT_R8G8_UINT, + VK_FORMAT_R8G8_SINT, + VK_FORMAT_R8G8_SRGB, + VK_FORMAT_R8G8B8_UNORM, + VK_FORMAT_R8G8B8_SNORM, + VK_FORMAT_R8G8B8_USCALED, + VK_FORMAT_R8G8B8_SSCALED, + VK_FORMAT_R8G8B8_UINT, + VK_FORMAT_R8G8B8_SINT, + VK_FORMAT_R8G8B8_SRGB, + VK_FORMAT_B8G8R8_UNORM, + VK_FORMAT_B8G8R8_SNORM, + VK_FORMAT_B8G8R8_USCALED, + VK_FORMAT_B8G8R8_SSCALED, + VK_FORMAT_B8G8R8_UINT, + VK_FORMAT_B8G8R8_SINT, + VK_FORMAT_B8G8R8_SRGB, + VK_FORMAT_R8G8B8A8_UNORM, + VK_FORMAT_R8G8B8A8_SNORM, + VK_FORMAT_R8G8B8A8_USCALED, + VK_FORMAT_R8G8B8A8_SSCALED, + VK_FORMAT_R8G8B8A8_UINT, + VK_FORMAT_R8G8B8A8_SINT, + VK_FORMAT_R8G8B8A8_SRGB, + VK_FORMAT_B8G8R8A8_UNORM, + VK_FORMAT_B8G8R8A8_SNORM, + VK_FORMAT_B8G8R8A8_USCALED, + VK_FORMAT_B8G8R8A8_SSCALED, + VK_FORMAT_B8G8R8A8_UINT, + VK_FORMAT_B8G8R8A8_SINT, + VK_FORMAT_B8G8R8A8_SRGB, + VK_FORMAT_A8B8G8R8_UNORM_PACK32, + VK_FORMAT_A8B8G8R8_SNORM_PACK32, + VK_FORMAT_A8B8G8R8_USCALED_PACK32, + VK_FORMAT_A8B8G8R8_SSCALED_PACK32, + VK_FORMAT_A8B8G8R8_UINT_PACK32, + VK_FORMAT_A8B8G8R8_SINT_PACK32, + VK_FORMAT_A8B8G8R8_SRGB_PACK32, + VK_FORMAT_A2R10G10B10_UNORM_PACK32, + VK_FORMAT_A2R10G10B10_SNORM_PACK32, + VK_FORMAT_A2R10G10B10_USCALED_PACK32, + VK_FORMAT_A2R10G10B10_SSCALED_PACK32, + VK_FORMAT_A2R10G10B10_UINT_PACK32, + VK_FORMAT_A2R10G10B10_SINT_PACK32, + VK_FORMAT_A2B10G10R10_UNORM_PACK32, + VK_FORMAT_A2B10G10R10_SNORM_PACK32, + VK_FORMAT_A2B10G10R10_USCALED_PACK32, + VK_FORMAT_A2B10G10R10_SSCALED_PACK32, + VK_FORMAT_A2B10G10R10_UINT_PACK32, + VK_FORMAT_A2B10G10R10_SINT_PACK32, + VK_FORMAT_R16_UNORM, + VK_FORMAT_R16_SNORM, + VK_FORMAT_R16_USCALED, + VK_FORMAT_R16_SSCALED, + VK_FORMAT_R16_UINT, + VK_FORMAT_R16_SINT, + VK_FORMAT_R16_SFLOAT, + VK_FORMAT_R16G16_UNORM, + VK_FORMAT_R16G16_SNORM, + VK_FORMAT_R16G16_USCALED, + VK_FORMAT_R16G16_SSCALED, + VK_FORMAT_R16G16_UINT, + VK_FORMAT_R16G16_SINT, + VK_FORMAT_R16G16_SFLOAT, + VK_FORMAT_R16G16B16_UNORM, + VK_FORMAT_R16G16B16_SNORM, + VK_FORMAT_R16G16B16_USCALED, + VK_FORMAT_R16G16B16_SSCALED, + VK_FORMAT_R16G16B16_UINT, + VK_FORMAT_R16G16B16_SINT, + VK_FORMAT_R16G16B16_SFLOAT, + VK_FORMAT_R16G16B16A16_UNORM, + VK_FORMAT_R16G16B16A16_SNORM, + VK_FORMAT_R16G16B16A16_USCALED, + VK_FORMAT_R16G16B16A16_SSCALED, + VK_FORMAT_R16G16B16A16_UINT, + VK_FORMAT_R16G16B16A16_SINT, + VK_FORMAT_R16G16B16A16_SFLOAT, + VK_FORMAT_R32_UINT, + VK_FORMAT_R32_SINT, + VK_FORMAT_R32_SFLOAT, + VK_FORMAT_R32G32_UINT, + VK_FORMAT_R32G32_SINT, + VK_FORMAT_R32G32_SFLOAT, + VK_FORMAT_R32G32B32_UINT, + VK_FORMAT_R32G32B32_SINT, + VK_FORMAT_R32G32B32_SFLOAT, + VK_FORMAT_R32G32B32A32_UINT, + VK_FORMAT_R32G32B32A32_SINT, + VK_FORMAT_R32G32B32A32_SFLOAT, + VK_FORMAT_R64_UINT, + VK_FORMAT_R64_SINT, + VK_FORMAT_R64_SFLOAT, + VK_FORMAT_R64G64_UINT, + VK_FORMAT_R64G64_SINT, + VK_FORMAT_R64G64_SFLOAT, + VK_FORMAT_R64G64B64_UINT, + VK_FORMAT_R64G64B64_SINT, + VK_FORMAT_R64G64B64_SFLOAT, + VK_FORMAT_R64G64B64A64_UINT, + VK_FORMAT_R64G64B64A64_SINT, + VK_FORMAT_R64G64B64A64_SFLOAT, + VK_FORMAT_B10G11R11_UFLOAT_PACK32, + VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, + VK_FORMAT_D16_UNORM, + VK_FORMAT_X8_D24_UNORM_PACK32, + VK_FORMAT_D32_SFLOAT, + VK_FORMAT_S8_UINT, + VK_FORMAT_D16_UNORM_S8_UINT, + VK_FORMAT_D24_UNORM_S8_UINT, + VK_FORMAT_D32_SFLOAT_S8_UINT, + VK_FORMAT_BC1_RGB_UNORM_BLOCK, + VK_FORMAT_BC1_RGB_SRGB_BLOCK, + VK_FORMAT_BC1_RGBA_UNORM_BLOCK, + VK_FORMAT_BC1_RGBA_SRGB_BLOCK, + VK_FORMAT_BC2_UNORM_BLOCK, + VK_FORMAT_BC2_SRGB_BLOCK, + VK_FORMAT_BC3_UNORM_BLOCK, + VK_FORMAT_BC3_SRGB_BLOCK, + VK_FORMAT_BC4_UNORM_BLOCK, + VK_FORMAT_BC4_SNORM_BLOCK, + VK_FORMAT_BC5_UNORM_BLOCK, + VK_FORMAT_BC5_SNORM_BLOCK, + VK_FORMAT_BC6H_UFLOAT_BLOCK, + VK_FORMAT_BC6H_SFLOAT_BLOCK, + VK_FORMAT_BC7_UNORM_BLOCK, + VK_FORMAT_BC7_SRGB_BLOCK, + VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, + VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, + VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK, + VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, + VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK, + VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, + VK_FORMAT_EAC_R11_UNORM_BLOCK, + VK_FORMAT_EAC_R11_SNORM_BLOCK, + VK_FORMAT_EAC_R11G11_UNORM_BLOCK, + VK_FORMAT_EAC_R11G11_SNORM_BLOCK, + VK_FORMAT_ASTC_4x4_UNORM_BLOCK, + VK_FORMAT_ASTC_4x4_SRGB_BLOCK, + VK_FORMAT_ASTC_5x4_UNORM_BLOCK, + VK_FORMAT_ASTC_5x4_SRGB_BLOCK, + VK_FORMAT_ASTC_5x5_UNORM_BLOCK, + VK_FORMAT_ASTC_5x5_SRGB_BLOCK, + VK_FORMAT_ASTC_6x5_UNORM_BLOCK, + VK_FORMAT_ASTC_6x5_SRGB_BLOCK, + VK_FORMAT_ASTC_6x6_UNORM_BLOCK, + VK_FORMAT_ASTC_6x6_SRGB_BLOCK, + VK_FORMAT_ASTC_8x5_UNORM_BLOCK, + VK_FORMAT_ASTC_8x5_SRGB_BLOCK, + VK_FORMAT_ASTC_8x6_UNORM_BLOCK, + VK_FORMAT_ASTC_8x6_SRGB_BLOCK, + VK_FORMAT_ASTC_8x8_UNORM_BLOCK, + VK_FORMAT_ASTC_8x8_SRGB_BLOCK, + VK_FORMAT_ASTC_10x5_UNORM_BLOCK, + VK_FORMAT_ASTC_10x5_SRGB_BLOCK, + VK_FORMAT_ASTC_10x6_UNORM_BLOCK, + VK_FORMAT_ASTC_10x6_SRGB_BLOCK, + VK_FORMAT_ASTC_10x8_UNORM_BLOCK, + VK_FORMAT_ASTC_10x8_SRGB_BLOCK, + VK_FORMAT_ASTC_10x10_UNORM_BLOCK, + VK_FORMAT_ASTC_10x10_SRGB_BLOCK, + VK_FORMAT_ASTC_12x10_UNORM_BLOCK, + VK_FORMAT_ASTC_12x10_SRGB_BLOCK, + VK_FORMAT_ASTC_12x12_UNORM_BLOCK, + VK_FORMAT_ASTC_12x12_SRGB_BLOCK, + + // Added sentinal value + VK_FORMAT_NONE; + + @Override + public int Value() { return ordinal(); } + } + + public enum VkPresentModeKHR implements IntValue { + VK_PRESENT_MODE_IMMEDIATE_KHR, + VK_PRESENT_MODE_MAILBOX_KHR, + VK_PRESENT_MODE_FIFO_KHR, + VK_PRESENT_MODE_FIFO_RELAXED_KHR, + + // Added sentinal value + VK_PRESENT_MODE_NONE; + + @Override + public int Value() { return ordinal(); } + } + + public enum VkResult implements IntValue { + VK_SUCCESS(0), + VK_NOT_READY(1), + VK_TIMEOUT(2), + VK_EVENT_SET(3), + VK_EVENT_RESET(4), + VK_INCOMPLETE(5), + VK_ERROR_OUT_OF_HOST_MEMORY(-1), + VK_ERROR_OUT_OF_DEVICE_MEMORY(-2), + VK_ERROR_INITIALIZATION_FAILED(-3), + VK_ERROR_DEVICE_LOST(-4), + VK_ERROR_MEMORY_MAP_FAILED(-5), + VK_ERROR_LAYER_NOT_PRESENT(-6), + VK_ERROR_EXTENSION_NOT_PRESENT(-7), + VK_ERROR_FEATURE_NOT_PRESENT(-8), + VK_ERROR_INCOMPATIBLE_DRIVER(-9), + VK_ERROR_TOO_MANY_OBJECTS(-10), + VK_ERROR_FORMAT_NOT_SUPPORTED(-11), + VK_ERROR_FRAGMENTED_POOL(-12), + VK_ERROR_UNKNOWN(-13), + VK_ERROR_OUT_OF_POOL_MEMORY(-1000069000), + VK_ERROR_INVALID_EXTERNAL_HANDLE(-1000072003), + VK_ERROR_FRAGMENTATION(-1000161000), + VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS(-1000257000), + VK_ERROR_SURFACE_LOST_KHR(-1000000000), + VK_ERROR_NATIVE_WINDOW_IN_USE_KHR(-1000000001), + VK_SUBOPTIMAL_KHR(1000001003), + VK_ERROR_OUT_OF_DATE_KHR(-1000001004), + VK_ERROR_INCOMPATIBLE_DISPLAY_KHR(-1000003001), + VK_ERROR_VALIDATION_FAILED_EXT(-1000011001), + VK_ERROR_INVALID_SHADER_NV(-1000012000), + VK_ERROR_INCOMPATIBLE_VERSION_KHR(-1000150000), + VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT(-1000158000), + VK_ERROR_NOT_PERMITTED_EXT(-1000174001), + VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT(-1000255000), + VK_THREAD_IDLE_KHR(1000268000), + VK_THREAD_DONE_KHR(1000268001), + VK_OPERATION_DEFERRED_KHR(1000268002), + VK_OPERATION_NOT_DEFERRED_KHR(1000268003), + VK_PIPELINE_COMPILE_REQUIRED_EXT(1000297000), + VK_ERROR_OUT_OF_POOL_MEMORY_KHR(-1000069000), + VK_ERROR_INVALID_EXTERNAL_HANDLE_KHR(-1000072003), + VK_ERROR_FRAGMENTATION_EXT(-1000161000), + VK_ERROR_INVALID_DEVICE_ADDRESS_EXT(-1000257000), + VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS_KHR(-1000257000), + VK_ERROR_PIPELINE_COMPILE_REQUIRED_EXT(1000297000), + + // Added sentinal value + VK_RESULT_UNKNOWN(0xF2345678); + + private final int val; + private VkResult(int v) { val = v; } + public static VkResult GetByValue(int value) { + for (VkResult e : VkResult.values()) { + if (value == e.val) { + return e; + } + } + return VK_RESULT_UNKNOWN; + } + public static boolean KnownValue(int value) { + for (VkResult e : VkResult.values()) { + if (value == e.val) { + return true; + } + } + return false; + } + @Override + public int Value() { return val; } + } + + public enum VkPhysicalDeviceType implements IntValue { + VK_PHYSICAL_DEVICE_TYPE_OTHER, + VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU, + VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU, + VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU, + VK_PHYSICAL_DEVICE_TYPE_CPU, + + // Added sentinel value + VK_PHYSICAL_DEVICE_TYPE_NONE; + + @Override + public int Value() { return ordinal(); } + + public static VkPhysicalDeviceType GetByValue(int value) { + if (value >= 0 && value < VK_PHYSICAL_DEVICE_TYPE_NONE.Value()) { + return VkPhysicalDeviceType.values()[value]; + } else { + return VK_PHYSICAL_DEVICE_TYPE_NONE; + } + } + } + + public enum VkObjectType implements IntValue { + VK_OBJECT_TYPE_UNKNOWN(0), + VK_OBJECT_TYPE_INSTANCE(1), + VK_OBJECT_TYPE_PHYSICAL_DEVICE(2), + VK_OBJECT_TYPE_DEVICE(3), + VK_OBJECT_TYPE_QUEUE(4), + VK_OBJECT_TYPE_SEMAPHORE(5), + VK_OBJECT_TYPE_COMMAND_BUFFER(6), + VK_OBJECT_TYPE_FENCE(7), + VK_OBJECT_TYPE_DEVICE_MEMORY(8), + VK_OBJECT_TYPE_BUFFER(9), + VK_OBJECT_TYPE_IMAGE(10), + VK_OBJECT_TYPE_EVENT(11), + VK_OBJECT_TYPE_QUERY_POOL(12), + VK_OBJECT_TYPE_BUFFER_VIEW(13), + VK_OBJECT_TYPE_IMAGE_VIEW(14), + VK_OBJECT_TYPE_SHADER_MODULE(15), + VK_OBJECT_TYPE_PIPELINE_CACHE(16), + VK_OBJECT_TYPE_PIPELINE_LAYOUT(17), + VK_OBJECT_TYPE_RENDER_PASS(18), + VK_OBJECT_TYPE_PIPELINE(19), + VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT(20), + VK_OBJECT_TYPE_SAMPLER(21), + VK_OBJECT_TYPE_DESCRIPTOR_POOL(22), + VK_OBJECT_TYPE_DESCRIPTOR_SET(23), + VK_OBJECT_TYPE_FRAMEBUFFER(24), + VK_OBJECT_TYPE_COMMAND_POOL(25), + // Provided by VK_VERSION_1_1(), + VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION(1000156000), + // Provided by VK_VERSION_1_1(), + VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE(1000085000), + // Provided by VK_KHR_surface(), + VK_OBJECT_TYPE_SURFACE_KHR(1000000000), + // Provided by VK_KHR_swapchain(), + VK_OBJECT_TYPE_SWAPCHAIN_KHR(1000001000), + // Provided by VK_KHR_display(), + VK_OBJECT_TYPE_DISPLAY_KHR(1000002000), + // Provided by VK_KHR_display(), + VK_OBJECT_TYPE_DISPLAY_MODE_KHR(1000002001), + // Provided by VK_EXT_debug_report(), + VK_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT(1000011000), + // Provided by VK_EXT_debug_utils(), + VK_OBJECT_TYPE_DEBUG_UTILS_MESSENGER_EXT(1000128000), + // Provided by VK_KHR_ray_tracing(), + VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR(1000165000), + // Provided by VK_EXT_validation_cache(), + VK_OBJECT_TYPE_VALIDATION_CACHE_EXT(1000160000), + // Provided by VK_INTEL_performance_query(), + VK_OBJECT_TYPE_PERFORMANCE_CONFIGURATION_INTEL(1000210000), + // Provided by VK_KHR_deferred_host_operations(), + VK_OBJECT_TYPE_DEFERRED_OPERATION_KHR(1000268000), + // Provided by VK_NV_device_generated_commands(), + VK_OBJECT_TYPE_INDIRECT_COMMANDS_LAYOUT_NV(1000277000), + // Provided by VK_EXT_private_data(), + VK_OBJECT_TYPE_PRIVATE_DATA_SLOT_EXT(1000295000), + // Provided by VK_KHR_descriptor_update_template(), + VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_KHR(VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE.Value()), + // Provided by VK_KHR_sampler_ycbcr_conversion(), + VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_KHR(VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION.Value()), + // Provided by VK_NV_ray_tracing(), + VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV(VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_KHR.Value()); + + private final int val; + private VkObjectType(int v) { val = v; } + @Override + public int Value() { return val; } + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/utils/CVKShaderUtils.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/utils/CVKShaderUtils.java new file mode 100644 index 0000000000..f6aa8fda98 --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/utils/CVKShaderUtils.java @@ -0,0 +1,539 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.utils; + +import static au.gov.asd.tac.constellation.visual.vulkan.utils.CVKUtils.*; +import static org.lwjgl.util.shaderc.Shaderc.*; +import static org.lwjgl.vulkan.VK10.*; +import static org.lwjgl.system.MemoryStack.stackPush; +import static org.lwjgl.system.MemoryUtil.NULL; +import au.gov.asd.tac.constellation.visual.vulkan.CVKDevice; +import au.gov.asd.tac.constellation.visual.vulkan.shaders.CVKShaderPlaceHolder; +import java.io.File; +import java.io.FileInputStream; +import org.lwjgl.system.NativeResource; +import java.io.InputStream; +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.LongBuffer; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.CodeSource; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import javax.xml.bind.DatatypeConverter; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.mutable.MutableLong; +import org.lwjgl.vulkan.VkShaderModuleCreateInfo; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.vulkan.VkDevice; +import org.openide.modules.Places; + + +public class CVKShaderUtils { + private static HashMap SHADER_MAP = new HashMap<>(); + private static MessageDigest md5 = null; + private static boolean shaderCompilerVerified = false; + + /** + * + * @param refClass Base class used as the root folder to search for shaderFile + * @param shaderFile Name of the shader to compile + * @param shaderKind Type of shader to compile + * @return A SPIRV object with the compiled shader in bytes + */ + public static SPIRV CompileShaderFile( final Class refClass, final String shaderFile, ShaderType shaderKind){ + InputStream source = refClass.getResourceAsStream(shaderFile); + try { + String stringBytes = new String(source.readAllBytes()); + return CompileShader(shaderFile, stringBytes, shaderKind); + } catch (Exception e) { + CVKGraphLogger.GetStaticLogger().LogException(e, "CompileShaderFile threw exception for shader: %s", shaderFile); + } + + return null; + } + + /** + * Uses the lwjgl-shaderc library to compile a shader into SPIRV format + * for Vulkan to use. + * + * @param filename Filename of the shader + * @param source Contents of the shader file in bytes + * @param shaderKind Type of shader being compiled + * @return A SPIRV object with the compiled shader in bytes + */ + public static SPIRV CompileShader(String filename, String source, ShaderType shaderKind) { + + long compiler = shaderc_compiler_initialize(); + + if(compiler == NULL) { + throw new RuntimeException("Failed to create shader compiler"); + } + + long result = shaderc_compile_into_spv(compiler, source, shaderKind.type, filename, "main", NULL); + + if(result == NULL) { + throw new RuntimeException("Failed to compile shader " + filename + " into SPIR-V"); + } + + if(shaderc_result_get_compilation_status(result) != shaderc_compilation_status_success) { + throw new RuntimeException("Failed to compile shader " + filename + " into SPIR-V:\n " + shaderc_result_get_error_message(result)); + } + + shaderc_compiler_release(compiler); + + return new SPIRV(result, shaderc_result_get_bytes(result)); + } + + /** + * ShaderType: Vertex, geometry, fragment + */ + public enum ShaderType { + VERTEX_SHADER(shaderc_glsl_vertex_shader), + GEOMETRY_SHADER(shaderc_glsl_geometry_shader), + FRAGMENT_SHADER(shaderc_glsl_fragment_shader), + SHADER_TYPE_UNKNOWN(Integer.MAX_VALUE); + + private final int type; + + ShaderType(int type) { + this.type = type; + } + } + + /** + * SPIRV class - holder for the bytecode of a compiled SPIRV shader + */ + public static final class SPIRV implements NativeResource { + + private final long handle; + private final ByteBuffer bytecode; + + public SPIRV(long handle, ByteBuffer bytecode) { + this.handle = handle; + this.bytecode = bytecode; + } + + public ByteBuffer bytecode() { + return bytecode; + } + + @Override + public void free() { + shaderc_result_release(handle); + } + } + + public static long CreateShaderModule(ByteBuffer spirvCode, VkDevice device) { + + try(MemoryStack stack = stackPush()) { + + VkShaderModuleCreateInfo createInfo = VkShaderModuleCreateInfo.callocStack(stack); + + createInfo.sType(VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO); + createInfo.pCode(spirvCode); + + LongBuffer pShaderModule = stack.mallocLong(1); + + int ret = vkCreateShaderModule(device, createInfo, null, pShaderModule); + if (VkFailed(ret)) { return VK_NULL_HANDLE; } + + return pShaderModule.get(0); + } + } + + private static Date GetShaderResourceFromJarModifiedDate(final String resourceName) { + try { + // Slashes will be in UNC form + final String uncResourceName = resourceName.replace("\\", "/"); + + CodeSource refClassSource = CVKShaderPlaceHolder.class.getProtectionDomain().getCodeSource(); + if (refClassSource != null) { + URL jarURL = refClassSource.getLocation(); + + HashMap env = new HashMap<>(); + try (FileSystem jarFS = FileSystems.newFileSystem(jarURL.toURI(), env)) { + for (var fileStore : jarFS.getFileStores()) { + String name = fileStore.name(); + if (name.endsWith("/")) { + name = name.substring(0, name.length() - 1); + } + + ZipInputStream zip = new ZipInputStream(new FileInputStream(new File(name))); + ZipEntry entry = zip.getNextEntry(); + while (entry != null) { + final String entryName = entry.getName(); + if (entryName.endsWith(uncResourceName)) { + return new Date(entry.getLastModifiedTime().toMillis()); + } + entry = zip.getNextEntry(); + } + } + } catch (Exception e) { + } + } + } catch (Exception e) { } + + return null; + } + + private static Date GetShaderResourceFromFileModifiedDate(final String resourceName) { + try { + final Path path = GetUserDirShaderResourcePath(resourceName); + if (Files.exists(path)) { + return new Date(path.toFile().lastModified()); + } + } catch (Exception e) {} + + return null; + } + + static private Path shaderPath = null; + private static Path GetUserDirShaderResourcePath(final String resourceName) { + if (shaderPath == null) { + Path constellationRoot = Paths.get(Places.getUserDirectory().getPath(), "../..").toAbsolutePath().normalize(); + + // split takes a regex expression, not a delimiter so we need to specify the dot literal + String[] packagePathParts = CVKShaderPlaceHolder.class.getCanonicalName().split("\\."); + + // Build the shader path + if (packagePathParts.length > 1) { + String[] shaderPathParts = new String[packagePathParts.length + 1]; + shaderPathParts[0] = "CoreVulkanDisplay\\src"; + + // ignore the placeholder class name + for (int i = 1; i < packagePathParts.length; ++i) { + shaderPathParts[i] = packagePathParts[i-1]; + } + shaderPathParts[packagePathParts.length] = resourceName; + + shaderPath = Paths.get(constellationRoot.toString(), shaderPathParts); + } + } + return shaderPath; + } + + public static enum CVKShaderResourceLocation { + NOT_FOUND, + JAR, + FILE + } + + private static ByteBuffer LoadShaderResourceFromJar(final String resourceName) { + try { + InputStream source = CVKShaderPlaceHolder.class.getResourceAsStream(resourceName); + if (source != null) { + byte[] allBytes = source.readAllBytes(); + ByteBuffer buffer = MemoryUtil.memAlloc(allBytes.length); + buffer.put(allBytes); + buffer.flip(); + return buffer; + } else { + return null; + } + } catch (Exception e) { + return null; + } + } + + private static ByteBuffer LoadShaderResourceFromFile(final String resourceName) { + try { + final Path path = GetUserDirShaderResourcePath(resourceName); + byte[] allBytes = Files.readAllBytes(path); + ByteBuffer buffer = MemoryUtil.memAlloc(allBytes.length); + buffer.put(allBytes); + buffer.flip(); + return buffer; + } catch (Exception e) { + return null; + } + } + + private static CVKShaderResourceLocation LoadCompiledShader(final String compiledFileName, final String md5FileName, CVKByteBufferPair buffers) { + Date compiledDateJar = GetShaderResourceFromJarModifiedDate(compiledFileName); + Date compiledDateFile = GetShaderResourceFromFileModifiedDate(compiledFileName); + Date md5DateJar = GetShaderResourceFromJarModifiedDate(md5FileName); + Date md5DateFile = GetShaderResourceFromFileModifiedDate(md5FileName); + + boolean fromJarValid = compiledDateJar != null && md5DateJar != null; + boolean fromFileValid = compiledDateFile != null && md5DateFile != null; + + CVKShaderResourceLocation newest = CVKShaderResourceLocation.NOT_FOUND; + + // There was a version in each, compare compile dates + if (fromJarValid && fromFileValid) { + if (compiledDateJar.after(compiledDateFile)) { + newest = CVKShaderResourceLocation.JAR; + } else { + newest = CVKShaderResourceLocation.FILE; + } + + } else if (fromJarValid) { + newest = CVKShaderResourceLocation.JAR; + } else if (fromFileValid) { + newest = CVKShaderResourceLocation.FILE; + } + + switch (newest) { + case JAR: + buffers.buf1 = LoadShaderResourceFromJar(compiledFileName); + buffers.buf2 = LoadShaderResourceFromJar(md5FileName); + break; + case FILE: + buffers.buf1 = LoadShaderResourceFromFile(compiledFileName); + buffers.buf2 = LoadShaderResourceFromFile(md5FileName); + break; + case NOT_FOUND: + default: + return CVKShaderResourceLocation.NOT_FOUND; + } + + if (buffers.buf1 == null || buffers.buf2 == null) { + newest = CVKShaderResourceLocation.NOT_FOUND; + } + + return newest; + } + + private static class CVKByteBufferPair { + private ByteBuffer buf1 = null; + private ByteBuffer buf2 = null; + + public void Destroy() { + if (buf1 != null) { + MemoryUtil.memFree(buf1); + buf1 = null; + } + if (buf2 != null) { + MemoryUtil.memFree(buf2); + buf2 = null; + } + } + } + + public static int LoadShader(String shaderName, MutableLong hShaderModule) { + hShaderModule.setValue(VK_NULL_HANDLE); + CVKGraphLogger.GetStaticLogger().fine("Loading shader '%s'", shaderName); + + if (SHADER_MAP.containsKey(shaderName)) { + CVKGraphLogger.GetStaticLogger().fine(" shader '%s' found in shader map", shaderName); + hShaderModule.setValue(SHADER_MAP.get(shaderName)); + return VK_SUCCESS; + } + CVKGraphLogger.GetStaticLogger().fine(" shader '%s' not found in shader map, looking on disk", shaderName); + + // First load the static MD5 digest instance + if (md5 == null) { + try { + md5 = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + CVKGraphLogger.GetStaticLogger().LogException(e, "Cannot load MD5 digest algorithm instance"); + return CVK_ERROR_MD5_ALGORITHM_LOAD_FAILED; + } + } + + final String md5FileName = String.format("compiled/%s.md5", shaderName); + final String compiledFileName = String.format("compiled/%s.spv", shaderName); + + // Load the shader source from the JAR as we will need it for compilation or checking MD5 + ByteBuffer sourceBytes = LoadShaderResourceFromJar(shaderName); + if (sourceBytes == null) { + CVKGraphLogger.GetStaticLogger().severe("Failed to load shader %s from resources", shaderName); + return CVK_ERROR_SHADER_SOURCE_LOAD_FAILED; + } + + // Hash the source + md5.reset(); + md5.update(sourceBytes); + ByteBuffer sourceMD5 = ByteBuffer.wrap(md5.digest()); + + // Load the compiledBytes and its MD5 if it exists + CVKByteBufferPair buffers = new CVKByteBufferPair(); + + CVKShaderResourceLocation compiledLocation = LoadCompiledShader(compiledFileName, md5FileName, buffers); + if (compiledLocation != CVKShaderResourceLocation.NOT_FOUND) { + if (0 == buffers.buf2.compareTo(sourceMD5)) { + // We have a match + hShaderModule.setValue(CVKShaderUtils.CreateShaderModule(buffers.buf1, CVKDevice.GetVkDevice())); + MemoryUtil.memFree(sourceBytes); + if (hShaderModule.longValue() == VK_NULL_HANDLE) { + CVKGraphLogger.GetStaticLogger().severe("Failed to create shader module for: %s", shaderName); + return CVK_ERROR_SHADER_MODULE; + } + buffers.Destroy(); + SHADER_MAP.put(shaderName, hShaderModule.longValue()); + CVKGraphLogger.GetStaticLogger().fine(" shader '%s' was up to date, loaded '%s' from disk", shaderName, compiledFileName); + return VK_SUCCESS; + } else { + if (CVKGraphLogger.GetStaticLogger().isLoggable(Level.FINE)) { + byte[] storedMD5Bytes = new byte[buffers.buf2.remaining()]; + buffers.buf2.get(storedMD5Bytes); + byte[] calculatedMD5Bytes = new byte[sourceMD5.remaining()]; + sourceMD5.get(calculatedMD5Bytes); + + String storedMD5 = DatatypeConverter.printHexBinary(storedMD5Bytes).toUpperCase(); + String calculatedMD5 = DatatypeConverter.printHexBinary(calculatedMD5Bytes).toUpperCase(); + + CVKGraphLogger.GetStaticLogger().fine(" compiled shader '%s' is out of date, compiling.\r\n Stored MD5 = %s\r\n Calculated MD5 = %s", + compiledFileName, storedMD5, calculatedMD5); + } + } + + buffers.Destroy(); + } else { + CVKGraphLogger.GetStaticLogger().fine(" no compiled shader found for '%s', compiling", shaderName); + } + + // Shader needs to be compiled + CVKGraphLogger.GetStaticLogger().warning("Compiling shader '%s' at runtime. Shaders should be precompiled and packaged with Constellation.", shaderName); + + // We have to compile, check we can load the shader compiler on this platform + if (!shaderCompilerVerified) { + long compiler = shaderc_compiler_initialize(); + if (compiler == NULL) { + CVKGraphLogger.GetStaticLogger().severe("Failed to initialise shader compiler"); + return CVK_ERROR_SHADER_COMPILER_LOAD_FAILED; + } + shaderc_compiler_release(compiler); + shaderCompilerVerified = true; + } + + // Figure out the shader stage + ShaderType shaderType = ShaderType.SHADER_TYPE_UNKNOWN; + String fileExtension = FilenameUtils.getExtension(shaderName).toLowerCase(); + switch (fileExtension) { + case "vs": + case "vert": + shaderType = ShaderType.VERTEX_SHADER; + break; + case "gs": + case "geom": + shaderType = ShaderType.GEOMETRY_SHADER; + break; + case "fs": + case "frag": + shaderType = ShaderType.FRAGMENT_SHADER; + break; + } + if (shaderType == ShaderType.SHADER_TYPE_UNKNOWN) { + MemoryUtil.memFree(sourceBytes); + CVKGraphLogger.GetStaticLogger().severe("Could not determine shader type for: %s", shaderName); + return CVK_ERROR_SHADER_TYPE_UNKNOWN; + } + + // Do the compilation + SPIRV spirv = CompileShaderFile(CVKShaderPlaceHolder.class, shaderName, shaderType); + if (spirv == null) { + CVKGraphLogger.GetStaticLogger().severe("Failed to compile shader %s", shaderName); + MemoryUtil.memFree(sourceBytes); + return CVK_ERROR_SHADER_COMPILATION; + } + + // Create a shader module we can use from the compiled bytes + hShaderModule.setValue(CVKShaderUtils.CreateShaderModule(spirv.bytecode(), CVKDevice.GetVkDevice())); + if (hShaderModule.longValue() == VK_NULL_HANDLE) { + CVKGraphLogger.GetStaticLogger().severe("Failed to create shader module for %s", shaderName); + MemoryUtil.memFree(sourceBytes); + return CVK_ERROR_SHADER_MODULE; + } + + // Save the MD5 and SPIRV + final Path md5Path = GetUserDirShaderResourcePath(md5FileName); + final Path compiledPath = GetUserDirShaderResourcePath(compiledFileName); + try { + md5Path.toFile().delete(); + } catch (Exception e) { + // Not fatal, carry on + } + try { + compiledPath.toFile().delete(); + } catch (Exception e) { + // Not fatal, carry on + } + + try { + File compiledFile = compiledPath.toFile(); + File parentDir = new File(compiledFile.getParent()); + parentDir.mkdirs(); + compiledFile.createNewFile(); + byte[] bytes = new byte[spirv.bytecode().capacity()]; + spirv.bytecode().get(bytes); + FileUtils.writeByteArrayToFile(compiledFile, bytes); + } catch (Exception e) { + CVKGraphLogger.GetStaticLogger().LogException(e, "Exception thrown writing %s to disk", compiledFileName); + MemoryUtil.memFree(sourceBytes); + return CVK_ERROR_SHADER_SPIRV_WRITE_FAILED; + } + + try { + File md5File = md5Path.toFile(); + File parentDir = new File(md5File.getParent()); + parentDir.mkdirs(); + md5File.createNewFile(); + byte[] bytes = new byte[sourceMD5.capacity()]; + sourceMD5.get(bytes); + FileUtils.writeByteArrayToFile(md5File, bytes); + } catch (Exception e) { + CVKGraphLogger.GetStaticLogger().LogException(e, "Exception thrown writing %s to disk", compiledFileName); + MemoryUtil.memFree(sourceBytes); + return CVK_ERROR_SHADER_MD5_WRITE_FAILED; + } + + // Cleanup, note the md5 is a wrapped buffer not allocated so doesn't need explicit freeing + try { + MemoryUtil.memFree(sourceBytes); + } catch (Exception e) { + CVKGraphLogger.GetStaticLogger().LogException(e, "Exception thrown releaseing ByteBuffers"); + } + + // SPIRV and MD5 written to disk, update map and return success + SHADER_MAP.put(shaderName, hShaderModule.longValue()); + return VK_SUCCESS; + } + + private void Destroy() { + if (SHADER_MAP != null) { + for (Entry el : SHADER_MAP.entrySet()) { + Long hShaderModule = el.getValue(); + if (hShaderModule != VK_NULL_HANDLE) { + vkDestroyShaderModule(CVKDevice.GetVkDevice(), hShaderModule, null); + } + } + SHADER_MAP.clear(); + SHADER_MAP = null; + } + } + + @SuppressWarnings("deprecation") + @Override + public void finalize() throws Throwable { + Destroy(); + super.finalize(); + } +} diff --git a/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/utils/CVKUtils.java b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/utils/CVKUtils.java new file mode 100644 index 0000000000..2b151bc0cd --- /dev/null +++ b/CoreVulkanDisplay/src/au/gov/asd/tac/constellation/visual/vulkan/utils/CVKUtils.java @@ -0,0 +1,528 @@ +/* + * Copyright 2010-2020 Australian Signals Directorate + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package au.gov.asd.tac.constellation.visual.vulkan.utils; + +import au.gov.asd.tac.constellation.utilities.graphics.Matrix44f; +import static org.lwjgl.system.MemoryUtil.*; +import static org.lwjgl.vulkan.VK10.*; +import static org.lwjgl.vulkan.EXTDebugUtils.VK_EXT_DEBUG_UTILS_EXTENSION_NAME; +import static org.lwjgl.vulkan.EXTMetalSurface.VK_EXT_METAL_SURFACE_EXTENSION_NAME; +import static org.lwjgl.vulkan.KHRSurface.VK_KHR_SURFACE_EXTENSION_NAME; +import static org.lwjgl.vulkan.KHRSwapchain.VK_KHR_SWAPCHAIN_EXTENSION_NAME; +import static org.lwjgl.vulkan.KHRWin32Surface.VK_KHR_WIN32_SURFACE_EXTENSION_NAME; +import static org.lwjgl.vulkan.KHRXlibSurface.VK_KHR_XLIB_SURFACE_EXTENSION_NAME; +import static org.lwjgl.system.MemoryStack.stackPush; +import au.gov.asd.tac.constellation.utilities.graphics.Vector3f; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.logging.Level; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.Platform; +import org.lwjgl.vulkan.VkLayerProperties; +import org.lwjgl.system.MemoryUtil; +import static org.lwjgl.vulkan.EXTDebugReport.VK_EXT_DEBUG_REPORT_EXTENSION_NAME; +import org.lwjgl.vulkan.VkClearColorValue; +import org.lwjgl.vulkan.VkClearValue; + + +public class CVKUtils { + + // Constants + public static final int UINT32_MAX = 0xFFFFFFFF; + public static final long UINT64_MAX = 0xFFFFFFFFFFFFFFFFL; + public static final Vector3f ZERO_3F = new Vector3f(0, 0, 0); + public static final Matrix44f IDENTITY_44F = Matrix44f.identity(); + + // @Nova's threaded glyph optimisation + public static final int NUM_CORES = Runtime.getRuntime().availableProcessors(); + + // Extra error codes that don't collide with VkResult + public static final int CVK_ERROR_INVALID_ARGS = 0xFFFF0000; + public static final int CVK_ERROR_IMAGE_TOO_SMALL_FOR_COPY = 0xFFFF0001; + public static final int CVK_ERROR_BUFFER_TOO_SMALL_FOR_COPY = 0xFFFF0002; + public static final int CVK_ERROR_INVALID_IMAGE = 0xFFFF0003; + public static final int CVK_ERROR_SHADER_COMPILATION = 0xFFFF0004; + public static final int CVK_ERROR_SHADER_MODULE = 0xFFFF0005; + public static final int CVK_SURFACE_UNSUPPORTED = 0xFFFF0006; + public static final int CVK_ERROR_OUT_OF_MEMORY = 0xFFFF0007; + public static final int CVK_ERROR_IMAGE_VIEW_CREATION_FAILED = 0xFFFF0008; + public static final int CVK_ERROR_SAVE_TO_FILE_FAILED = 0xFFFF0009; + public static final int CVK_ERROR_IMAGE_GET_ID_FAILED = 0xFFFF000A; + public static final int CVK_ERROR_DEST_IMAGE_CREATE_FAILED = 0xFFFF000B; + public static final int CVK_ERROR_HITTEST_SOURCE_IMAGE_CREATE_FAILED = 0xFFFF000C; + public static final int CVK_ERROR_HITTEST_DEPTH_IMAGE_CREATE_FAILED = 0xFFFF000D; + public static final int CVK_ERROR_MD5_ALGORITHM_LOAD_FAILED = 0xFFFF000E; + public static final int CVK_ERROR_SHADER_SOURCE_LOAD_FAILED = 0xFFFF000F; + public static final int CVK_ERROR_SHADER_COMPILER_LOAD_FAILED = 0xFFFF0010; + public static final int CVK_ERROR_SHADER_TYPE_UNKNOWN = 0xFFFF0011; + public static final int CVK_ERROR_SHADER_SPIRV_WRITE_FAILED = 0xFFFF0012; + public static final int CVK_ERROR_SHADER_MD5_WRITE_FAILED = 0xFFFF0013; + public static final int CVK_ERROR_SHADER_SOURCE_FILE_NOT_FOUND = 0xFFFF0014; + public static final int CVK_ERROR_RENDERABLE_INITIALISATION_FAILED = 0xFFFF0015; + public static final int CVK_ERROR_ICON_ATLAS_COPY_TIMEDOUT = 0xFFFF0016; + public static final int CVK_ERROR_ICON_ATLAS_SAMPLER_CREATION_FAILED = 0xFFFF0017; + public static final int CVK_ERROR_ICON_ATLAS_UNSUPPORTED_ICON_FORMAT = 0xFFFF0018; + + // Enable this for additional logging, thread verification and other checks + public static final boolean CVK_DEBUGGING; + public static final Level CVK_ALLOCATION_LOG_LEVEL; + public static final Level CVK_DEFAULT_LOG_LEVEL; + public static int CVK_VKALLOCATIONS = 0; + public static final boolean CVK_ICON_ATLAS_CACHING; + + + // Initialise static vars from config + static { + boolean debugging; + try { + debugging = Boolean.parseBoolean(System.getProperty("debug_renderer")); + } catch (Throwable t) { + debugging = false; + } + CVK_DEBUGGING = debugging; + + Level allocationLogLevel; + try { + allocationLogLevel = System.getProperty("renderer_allocation_log_level") != null ? + Level.parse(System.getProperty("renderer_allocation_log_level").toUpperCase()) : + Level.OFF; + } catch (Throwable t) { + allocationLogLevel = Level.OFF; + } + CVK_ALLOCATION_LOG_LEVEL = allocationLogLevel; + + Level defaultLogLevel; + try { + defaultLogLevel = System.getProperty("renderer_log_level") != null ? + Level.parse(System.getProperty("renderer_log_level").toUpperCase()) : + Level.OFF; + } catch (Throwable t) { + defaultLogLevel = Level.OFF; + } + CVK_DEFAULT_LOG_LEVEL = defaultLogLevel; + + boolean iconCaching; + try { + iconCaching = Boolean.parseBoolean(System.getProperty("cache_icon_atlas")); + } catch (Throwable t) { + iconCaching = false; + } + CVK_ICON_ATLAS_CACHING = iconCaching; + } + + + public static void LogStackTrace(Level level) { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + for (StackTraceElement el : stackTrace) { + CVKGraphLogger.GetStaticLogger().log(level, el.toString()); + } + } + public static String GetMethodName(final int depth) { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + if (stackTrace.length > depth) { + return String.format("%s (%s:%d)", + stackTrace[depth].getMethodName(), + stackTrace[depth].getFileName(), + stackTrace[depth].getLineNumber()); + } else { + return "UNABLE TO GET METHOD NAME"; + } + } + public static String GetCurrentMethodName() { + return GetMethodName(3); + } + public static String GetParentMethodName() { + return GetMethodName(4); + } + + public static boolean LayerPresent(VkLayerProperties.Buffer layers, String layer) { + for (int i = 0; i < layers.limit(); ++i) { + if (layers.get(i).layerNameString().equals(layer)) { + return true; + } + } + return false; + } + + public static void checkVKret(int retCode) throws IllegalStateException { + if (retCode != VK_SUCCESS) { + String desc; + if (CVKMissingEnums.VkResult.KnownValue(retCode)) { + CVKMissingEnums.VkResult result = CVKMissingEnums.VkResult.GetByValue(retCode); + desc = result.name(); + } + else { + desc = String.format("Vulkan error [0x%X]", retCode); + } + CVKGraphLogger.GetStaticLogger().severe(String.format("SEVERE: checkVKret failed, %s\nStack:", desc)); + LogStackTrace(Level.SEVERE); + CVKGraphLogger.GetStaticLogger().severe("Exception incoming"); + + throw new IllegalStateException(desc); + } + } + +/** + * Returns a PointerBuffer of extensions required for our physical device to + * be able to render on a surface provided by an AWT canvas. + *

+ * To use an AWT canvas as a rendering surface we need a VkSurfaceKHR. When + * creating the VkInstance we need the following extensions. Note that KHR + * indicates a KHRonos extension. + * + * @TODO: should be in a loop like layers that enumerates first. Also add + * VK_EXT_DEBUG_REPORT_EXTENSION_NAME? + * + * @param stack + * @return PointerBuffer of extensions allocated on the provided stack + */ + public static PointerBuffer GetRequiredVKPhysicalDeviceExtensions(MemoryStack stack) { + ByteBuffer VK_EXT_DEBUG_UTILS_EXTENSION = stack.UTF8(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); + ByteBuffer VK_KHR_SURFACE_EXTENSION = stack.UTF8(VK_KHR_SURFACE_EXTENSION_NAME); + ByteBuffer VK_EXT_DEBUG_REPORT_EXTENSION = stack.UTF8(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); + + ByteBuffer VK_KHR_OS_SURFACE_EXTENSION; + + switch (Platform.get()) { + case WINDOWS: + VK_KHR_OS_SURFACE_EXTENSION = stack.UTF8(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); + break; + case LINUX: + VK_KHR_OS_SURFACE_EXTENSION = stack.UTF8(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); + break; + case MACOSX: + VK_KHR_OS_SURFACE_EXTENSION = stack.UTF8(VK_EXT_METAL_SURFACE_EXTENSION_NAME); + break; + default: + throw new RuntimeException("Unknown platform trying it initialise Vulkan"); + } + + PointerBuffer pbEnabledExtensionNames; + if (CVK_DEBUGGING) { + pbEnabledExtensionNames = stack.mallocPointer(4); + pbEnabledExtensionNames.put(VK_EXT_DEBUG_UTILS_EXTENSION); + pbEnabledExtensionNames.put(VK_EXT_DEBUG_REPORT_EXTENSION); + pbEnabledExtensionNames.put(VK_KHR_SURFACE_EXTENSION); + pbEnabledExtensionNames.put(VK_KHR_OS_SURFACE_EXTENSION); + } else { + pbEnabledExtensionNames = stack.mallocPointer(2); + pbEnabledExtensionNames.put(VK_KHR_SURFACE_EXTENSION); + pbEnabledExtensionNames.put(VK_KHR_OS_SURFACE_EXTENSION); + } + + // Flipping an org.lwjgl.system.CustomBuffer ends writes and prepares it for reads. In practice + // this resets the current index to 0 + pbEnabledExtensionNames.flip(); + return pbEnabledExtensionNames; + } + + public static PointerBuffer GetRequiredVKLogicalDeviceExtensions(MemoryStack stack) { + PointerBuffer pbEnabledExtensionNames = stack.mallocPointer(1); + pbEnabledExtensionNames.put(stack.UTF8(VK_KHR_SWAPCHAIN_EXTENSION_NAME)); + pbEnabledExtensionNames.flip(); + return pbEnabledExtensionNames; + } + + + /** + * Returns a list of validation layers to be added to the Vulkan stack for + * debugging. + *

+ * Vulkan has no error or state checking by default to make it more + * performant. It does however have the concept of layers that can be added + * to the API for validation or other purposes. Here we will first look for + * validation layers made available by the Vulkan SDK and if that's not + * installed we'll fall back to look for LunarG validation layers. + * + * @param stack + * @return PointerBuffer of validation layers allocated on the provided + * stack + */ + private final static Level VALIDATION_LAYER_LOG_LEVEL = Level.INFO; + private static PointerBuffer pbVKValidationLayers = null; + public static PointerBuffer InitVKValidationLayers() { + if (pbVKValidationLayers == null) { + try (MemoryStack stack = stackPush()) { + IntBuffer pInt = stack.mallocInt(1); + pInt.put(0); + pInt.flip(); + + // Get the count of available layers + int ret = vkEnumerateInstanceLayerProperties(pInt, null); + checkVKret(ret); + int layerCount = pInt.get(0); + if (CVKGraphLogger.GetStaticLogger().isLoggable(VALIDATION_LAYER_LOG_LEVEL)) { + CVKGraphLogger.GetStaticLogger().log(VALIDATION_LAYER_LOG_LEVEL, "Vulkan has %d available layers.", layerCount); + } + + // Get available layers + VkLayerProperties.Buffer availableLayers = VkLayerProperties.mallocStack(layerCount, stack); + checkVKret(vkEnumerateInstanceLayerProperties(pInt, availableLayers)); + if (CVKGraphLogger.GetStaticLogger().isLoggable(VALIDATION_LAYER_LOG_LEVEL)) { + for (int i = 0; i < layerCount; ++i) { + availableLayers.position(i); + String layerDesc = availableLayers.descriptionString(); + CVKGraphLogger.GetStaticLogger().log(VALIDATION_LAYER_LOG_LEVEL, "\tVulkan layer %d: %s", i, layerDesc); + } + } + + // Select the best set of validation layers. If VK_LAYER_KHRONOS_validation + // is not available then fall back to VK_LAYER_LUNARG_standard_validation, if + // it's not available try a bunch of older validation layers. + // TODO: these are borrowed from https://github.com/LWJGL/lwjgl3/blob/master/modules/samples/src/test/java/org/lwjgl/demo/vulkan/HelloVulkan.java + // Find some logic behind layer selection and codify it here. + ArrayList validationLayers = new ArrayList<>(); + if (LayerPresent(availableLayers, "VK_LAYER_KHRONOS_validation")) { + validationLayers.add("VK_LAYER_KHRONOS_validation"); + } else if (LayerPresent(availableLayers, "VK_LAYER_LUNARG_standard_validation")) { + validationLayers.add("VK_LAYER_LUNARG_standard_validation"); + } else { + if (LayerPresent(availableLayers, "VK_LAYER_GOOGLE_threading")) { + validationLayers.add("VK_LAYER_GOOGLE_threading"); + } + if (LayerPresent(availableLayers, "VK_LAYER_LUNARG_parameter_validation")) { + validationLayers.add("VK_LAYER_LUNARG_parameter_validation"); + } + if (LayerPresent(availableLayers, "VK_LAYER_LUNARG_core_validation")) { + validationLayers.add("VK_LAYER_LUNARG_core_validation"); + } + if (LayerPresent(availableLayers, "VK_LAYER_LUNARG_object_tracker")) { + validationLayers.add("VK_LAYER_LUNARG_object_tracker"); + } + if (LayerPresent(availableLayers, "VK_LAYER_GOOGLE_unique_objects")) { + validationLayers.add("VK_LAYER_GOOGLE_unique_objects"); + } + } + + pbVKValidationLayers = memAllocPointer(validationLayers.size()); + validationLayers.forEach(layer -> { + pbVKValidationLayers.put(memASCII(layer)); + }); + pbVKValidationLayers.flip(); + } + } + + return pbVKValidationLayers; + } + + + //========================================================================== + // DEBUGGING CODE - ASSERTS + // + // Note the code below was originally profiled in CVKCanvas but moved here as + // they inform the explanation for the different assert methods. + // + // The blob below ran 5-6 times faster for CVKAssertNotNull when CVK_DEBUGGING + // was false. This may be due to Java knowing it doesn't need to dereference + // the object when the body of CVKAssertNotNull is essentially a noop when + // CVK_DEBUGGING is false. + // + // final int COUNT = 10000000; + // long startCount = System.nanoTime(); + // for (int i = 0; i < COUNT; ++i) { + // CVKAssert(cvkRenderer != null); + // } + // long endCount = System.nanoTime(); + // float elapsedMilliSeconds = (endCount-startCount) / 1000.0f; + // cvkRenderer.GetLogger().severe("%d CVKAssert(cvkRenderer != null) took %f milliseconds with CVK_DEBUGGING = %s", + // COUNT, elapsedMilliSeconds, CVK_DEBUGGING ? "true" : "false"); + // + // startCount = System.nanoTime(); + // for (int i = 0; i < COUNT; ++i) { + // CVKAssertNotNull(cvkRenderer); + // } + // endCount = System.nanoTime(); + // elapsedMilliSeconds = (endCount-startCount) / 1000.0f; + // cvkRenderer.GetLogger().severe("%d CVKAssertNotNull(cvkRenderer != null) took %f milliseconds with CVK_DEBUGGING = %s", + // COUNT, elapsedMilliSeconds, CVK_DEBUGGING ? "true" : "false"); + // + // + // This hokey bit of code confirms that JAVA isn't evaluating the body of + // VerifyInRenderThread() if CVK_DEBUGGING is false as the timings are more + // than 1000 faster when CVK_DEBUGGING is false. + // + // final int COUNT = 10000000; + // long startCount = System.nanoTime(); + // for (int i = 0; i < COUNT; ++i) { + // cvkRenderer.VerifyInRenderThread(); + // } + // long endCount = System.nanoTime(); + // float elapsedMilliSeconds = (endCount-startCount) / 1000.0f; + // cvkRenderer.GetLogger().severe("%d VerifyInRenderThread took %f milliseconds with CVK_DEBUGGING = %s", + // COUNT, elapsedMilliSeconds, CVK_DEBUGGING ? "true" : "false"); + //========================================================================== + + + public static void CVKAssert(boolean exprResult) { + if (CVK_DEBUGGING && !exprResult) { + String msg = "CVKAssert fired"; + + // If run from Netbeans the system console is null + if (System.console() == null) { + throw new RuntimeException(msg); + } else { + CVKGraphLogger.GetStaticLogger().severe(msg); + LogStackTrace(Level.SEVERE); + } + } + } + + public static void CVKAssertNotNull(Object object) { + if (CVK_DEBUGGING && object == null) { + String msg = "CVKAssertNotNull(object) fired"; + + // If run from Netbeans the system console is null + if (System.console() == null) { + throw new RuntimeException(msg); + } else { + CVKGraphLogger.GetStaticLogger().severe(msg); + LogStackTrace(Level.SEVERE); + } + } + } + + public static void CVKAssertNotNull(long handle) { + if (CVK_DEBUGGING && handle == VK_NULL_HANDLE) { + String msg = "CVKAssertNotNull(handle) fired"; + + // If run from Netbeans the system console is null + if (System.console() == null) { + throw new RuntimeException(msg); + } else { + CVKGraphLogger.GetStaticLogger().severe(msg); + LogStackTrace(Level.SEVERE); + } + } + } + + public static void CVKAssertNull(Object object) { + if (CVK_DEBUGGING && object != null) { + // If run from Netbeans the system console is null + String msg = String.format("CVKAssertNull(object) (%s was supposed to be null) fired", object.toString()); + if (System.console() == null) { + throw new RuntimeException(msg); + } else { + CVKGraphLogger.GetStaticLogger().severe(msg); + LogStackTrace(Level.SEVERE); + } + } + } + + public static void CVKAssertNull(long handle) { + if (CVK_DEBUGGING && handle != VK_NULL_HANDLE) { + // If run from Netbeans the system console is null + String msg = String.format("CVKAssertNull(handle) (%d(0x%016X) was supposed to be null) fired", handle, handle); + if (System.console() == null) { + throw new RuntimeException(msg); + } else { + CVKGraphLogger.GetStaticLogger().severe(msg); + LogStackTrace(Level.SEVERE); + } + } + } + + public static void CVKAssertNotNullOrEmpty(String str) { + if (CVK_DEBUGGING) { + if (str == null) { + // If run from Netbeans the system console is null + String msg = String.format("CVKAssertNotNullOrEmpty(string) fired (string was null)"); + if (System.console() == null) { + throw new RuntimeException(msg); + } else { + CVKGraphLogger.GetStaticLogger().severe(msg); + LogStackTrace(Level.SEVERE); + } + } else if (str.isEmpty()) { + // If run from Netbeans the system console is null + String msg = String.format("CVKAssertNotNullOrEmpty(string) fired (string was empty)"); + if (System.console() == null) { + throw new RuntimeException(msg); + } else { + CVKGraphLogger.GetStaticLogger().severe(msg); + LogStackTrace(Level.SEVERE); + } + } + } + } + + /** + * + * @param refClass + * @param resourceName + * @return + * @throws IOException + */ + public static ByteBuffer LoadFileToDirectBuffer(final Class refClass, final String resourceName) throws IOException { + InputStream source = refClass.getResourceAsStream(resourceName); + byte[] allBytes = source.readAllBytes(); + + ByteBuffer buffer = MemoryUtil.memAlloc(allBytes.length); + buffer.put(allBytes); + buffer.flip(); + return buffer; + } + + + + + public static boolean VkSucceeded(int ret) { return ret == VK_SUCCESS; } + public static boolean VkFailed(int ret) { return !VkSucceeded(ret); } + + public static VkClearValue getClearValueColor(Vector3f clearColor){ + + VkClearValue clearValues = VkClearValue.calloc(); + clearValues.color() + .float32(0, clearColor.getX()) + .float32(1, clearColor.getY()) + .float32(2, clearColor.getZ()) + .float32(3, 1.0f); + + return clearValues; + } + + public static VkClearColorValue getClearColorValue(){ + + VkClearColorValue clearValues = VkClearColorValue.calloc(); + clearValues + .float32(0, 0.0f) + .float32(1, 0.0f) + .float32(2, 0.0f) + .float32(3, 1.0f); + + return clearValues; + } + + public static VkClearValue getClearValueDepth(){ + + VkClearValue clearValues = VkClearValue.calloc(); + clearValues.depthStencil() + .depth(1.0f); + + return clearValues; + } + + public static void PutMatrix44f(final ByteBuffer buffer, final Matrix44f mtx) { + for (int iRow = 0; iRow < 4; ++iRow) { + for (int iCol = 0; iCol < 4; ++iCol) { + buffer.putFloat(mtx.get(iRow, iCol)); + } + } + } +} diff --git a/ProjectUpdater/src/build.xml b/ProjectUpdater/src/build.xml index 92df6bb0be..646215e9e8 100644 --- a/ProjectUpdater/src/build.xml +++ b/ProjectUpdater/src/build.xml @@ -11,7 +11,7 @@ - + @@ -78,6 +78,15 @@ src="https://raw.githubusercontent.com/constellation-app/third-party-dependencies/master/Processing%204.0/core.jar" dest="${suite.dir}/CoreDependencies/release/modules/ext" usetimestamp="true"/> + + + + + + + diff --git a/nbproject/project.properties b/nbproject/project.properties index eb8259d7d1..98f777aa98 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -49,6 +49,7 @@ modules=\ ${project.au.gov.asd.tac.constellation.views.timeline}:\ ${project.au.gov.asd.tac.constellation.views.webview}:\ ${project.au.gov.asd.tac.constellation.visual.opengl}:\ + ${project.au.gov.asd.tac.constellation.visual.vulkan}:\ ${project.au.gov.asd.tac.constellation.webserver} project.au.gov.asd.tac.constellation.dependencies=CoreDependencies @@ -88,6 +89,7 @@ project.au.gov.asd.tac.constellation.views.tableview=CoreTableView project.au.gov.asd.tac.constellation.views.timeline=CoreTimelineView project.au.gov.asd.tac.constellation.views.webview=CoreWebView project.au.gov.asd.tac.constellation.visual.opengl=CoreOpenGLDisplay +project.au.gov.asd.tac.constellation.visual.vulkan=CoreVulkanDisplay project.au.gov.asd.tac.constellation.webserver=CoreWebServer continue.after.failing.tests=false diff --git a/renderer.conf b/renderer.conf new file mode 100644 index 0000000000..1cacd71682 --- /dev/null +++ b/renderer.conf @@ -0,0 +1,5 @@ +factory=au.gov.asd.tac.constellation.graph.interaction.visual.VKInteractiveVisualManagerFactory +debug_renderer=false +renderer_log_level=OFF +renderer_allocation_log_level=FINEST +cache_icon_atlas=true