Implement support for pre-built SPIR-V binaries
vk-build-programs utility can be used to build and store SPIR-V
binaries.
If glslang is not available, test framework will attempt to load
pre-built binaries from vulkan/prebuilt directory.
scripts/build_vulkan_programs.py can be used to populate
data/vulkan/prebuilt directory with SPIR-V binaries.
Change-Id: I1e998bba0e0021d0e5b6da35ed53c54a61207bff
diff --git a/data/vulkan/.gitignore b/data/vulkan/.gitignore
new file mode 100644
index 0000000..21ba210
--- /dev/null
+++ b/data/vulkan/.gitignore
@@ -0,0 +1 @@
+prebuilt
diff --git a/framework/vulkan/CMakeLists.txt b/framework/vulkan/CMakeLists.txt
index b3380b5..e615e4a 100644
--- a/framework/vulkan/CMakeLists.txt
+++ b/framework/vulkan/CMakeLists.txt
@@ -19,6 +19,8 @@
vkDeviceUtil.hpp
vkGlslToSpirV.cpp
vkGlslToSpirV.hpp
+ vkBinaryRegistry.cpp
+ vkBinaryRegistry.hpp
)
# \note Code interfacing with glslang needs to include third-party headers
diff --git a/framework/vulkan/vkBinaryRegistry.cpp b/framework/vulkan/vkBinaryRegistry.cpp
new file mode 100644
index 0000000..a43864a
--- /dev/null
+++ b/framework/vulkan/vkBinaryRegistry.cpp
@@ -0,0 +1,100 @@
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Vulkan Utilities
+ * -----------------------------------------------
+ *
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.
+ *
+ *//*!
+ * \file
+ * \brief Program binary registry.
+ *//*--------------------------------------------------------------------*/
+
+#include "vkBinaryRegistry.hpp"
+#include "tcuResource.hpp"
+#include "deFilePath.hpp"
+
+#include <fstream>
+
+namespace vk
+{
+
+using std::string;
+using std::vector;
+
+static string getProgramFileName (const ProgramIdentifier& id)
+{
+ // \todo [2015-06-26 pyry] Sanitize progName
+ return id.testCasePath + "." + id.programName + ".spirv";
+}
+
+// BinaryRegistryWriter
+
+BinaryRegistryWriter::BinaryRegistryWriter (const std::string& dstPath)
+ : m_dstPath(dstPath)
+{
+}
+
+BinaryRegistryWriter::~BinaryRegistryWriter (void)
+{
+}
+
+void BinaryRegistryWriter::storeProgram (const ProgramIdentifier& id, const ProgramBinary& binary)
+{
+ const string fullPath = de::FilePath::join(m_dstPath, getProgramFileName(id)).getPath();
+ std::ofstream out (fullPath.c_str(), std::ios_base::binary);
+
+ if (!out.is_open() || !out.good())
+ throw tcu::Exception("Failed to open " + fullPath);
+
+ out.write((const char*)binary.getBinary(), binary.getSize());
+ out.close();
+}
+
+// BinaryRegistryReader
+
+BinaryRegistryReader::BinaryRegistryReader (const tcu::Archive& archive, const std::string& srcPath)
+ : m_archive (archive)
+ , m_srcPath (srcPath)
+{
+}
+
+BinaryRegistryReader::~BinaryRegistryReader (void)
+{
+}
+
+ProgramBinary* BinaryRegistryReader::loadProgram (const ProgramIdentifier& id) const
+{
+ const string fullPath = de::FilePath::join(m_srcPath, getProgramFileName(id)).getPath();
+
+ try
+ {
+ de::UniquePtr<tcu::Resource> progRes (m_archive.getResource(fullPath.c_str()));
+ const int progSize = progRes->getSize();
+ vector<deUint8> bytes (progSize);
+
+ TCU_CHECK_INTERNAL(!bytes.empty());
+
+ progRes->read(&bytes[0], progSize);
+
+ return new ProgramBinary(vk::PROGRAM_FORMAT_SPIRV, bytes.size(), &bytes[0]);
+ }
+ catch (const tcu::ResourceError&)
+ {
+ throw ProgramNotFoundException(id);
+ }
+}
+
+
+} // vk
diff --git a/framework/vulkan/vkBinaryRegistry.hpp b/framework/vulkan/vkBinaryRegistry.hpp
new file mode 100644
index 0000000..500b2ed
--- /dev/null
+++ b/framework/vulkan/vkBinaryRegistry.hpp
@@ -0,0 +1,85 @@
+#ifndef _VKBINARYREGISTRY_HPP
+#define _VKBINARYREGISTRY_HPP
+/*-------------------------------------------------------------------------
+ * drawElements Quality Program Vulkan Utilities
+ * -----------------------------------------------
+ *
+ * Copyright 2015 The Android Open Source Project
+ *
+ * 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.
+ *
+ *//*!
+ * \file
+ * \brief Program binary registry.
+ *//*--------------------------------------------------------------------*/
+
+#include "vkDefs.hpp"
+#include "vkPrograms.hpp"
+
+namespace tcu
+{
+class Archive;
+}
+
+namespace vk
+{
+
+struct ProgramIdentifier
+{
+ std::string testCasePath;
+ std::string programName;
+
+ ProgramIdentifier (const std::string& testCasePath_, const std::string& programName_)
+ : testCasePath (testCasePath_)
+ , programName (programName_)
+ {
+ }
+};
+
+class ProgramNotFoundException : public tcu::ResourceError
+{
+public:
+ ProgramNotFoundException (const ProgramIdentifier& id)
+ : tcu::ResourceError("Program " + id.testCasePath + " / '" + id.programName + "' not found")
+ {
+ }
+};
+
+class BinaryRegistryReader
+{
+public:
+ BinaryRegistryReader (const tcu::Archive& archive, const std::string& srcPath);
+ ~BinaryRegistryReader (void);
+
+ ProgramBinary* loadProgram (const ProgramIdentifier& id) const;
+
+private:
+ const tcu::Archive& m_archive;
+ const std::string& m_srcPath;
+};
+
+class BinaryRegistryWriter
+{
+public:
+ BinaryRegistryWriter (const std::string& dstPath);
+ ~BinaryRegistryWriter (void);
+
+ void storeProgram (const ProgramIdentifier& id, const ProgramBinary& binary);
+
+private:
+ const std::string m_dstPath;
+};
+
+} // vk
+
+#endif // _VKBINARYREGISTRY_HPP
diff --git a/modules/vulkan/CMakeLists.txt b/modules/vulkan/CMakeLists.txt
index 4af36bd..a683732 100644
--- a/modules/vulkan/CMakeLists.txt
+++ b/modules/vulkan/CMakeLists.txt
@@ -40,3 +40,5 @@
endif ()
add_deqp_module(deqp-vk "${DEQP_VK_SRCS}" "${DEQP_VK_LIBS}" vktTestPackageEntry.cpp)
+
+add_data_dir(deqp-vk ../../data/vulkan vulkan)
diff --git a/modules/vulkan/vktBuildPrograms.cpp b/modules/vulkan/vktBuildPrograms.cpp
index c6593f8..38c77b1 100644
--- a/modules/vulkan/vktBuildPrograms.cpp
+++ b/modules/vulkan/vktBuildPrograms.cpp
@@ -18,7 +18,7 @@
*
*//*!
* \file
- * \brief
+ * \brief Utility for pre-compiling source programs to SPIR-V
*//*--------------------------------------------------------------------*/
#include "tcuDefs.hpp"
@@ -29,8 +29,11 @@
#include "tcuTestHierarchyIterator.hpp"
#include "deUniquePtr.hpp"
#include "vkPrograms.hpp"
+#include "vkBinaryRegistry.hpp"
#include "vktTestCase.hpp"
#include "vktTestPackage.hpp"
+#include "deUniquePtr.hpp"
+#include "deCommandLine.hpp"
#include <iostream>
@@ -48,11 +51,35 @@
return new tcu::TestPackageRoot(testCtx, children);
}
-void buildPrograms (tcu::TestContext& testCtx)
+enum BuildMode
+{
+ BUILDMODE_BUILD = 0,
+ BUILDMODE_VERIFY,
+
+ BUILDMODE_LAST
+};
+
+struct BuildStats
+{
+ int numSucceeded;
+ int numFailed;
+
+ BuildStats (void)
+ : numSucceeded (0)
+ , numFailed (0)
+ {
+ }
+};
+
+BuildStats buildPrograms (tcu::TestContext& testCtx, const std::string& dstPath, BuildMode mode)
{
const UniquePtr<tcu::TestPackageRoot> root (createRoot(testCtx));
tcu::DefaultHierarchyInflater inflater (testCtx);
tcu::TestHierarchyIterator iterator (*root, inflater, testCtx.getCommandLine());
+ const tcu::DirArchive srcArchive (dstPath.c_str());
+ UniquePtr<vk::BinaryRegistryWriter> writer (mode == BUILDMODE_BUILD ? new vk::BinaryRegistryWriter(dstPath) : DE_NULL);
+ UniquePtr<vk::BinaryRegistryReader> reader (mode == BUILDMODE_VERIFY ? new vk::BinaryRegistryReader(srcArchive, "") : DE_NULL);
+ BuildStats stats;
while (iterator.getState() != tcu::TestHierarchyIterator::STATE_FINISHED)
{
@@ -60,46 +87,107 @@
tcu::isTestNodeTypeExecutable(iterator.getNode()->getNodeType()))
{
const TestCase* const testCase = dynamic_cast<TestCase*>(iterator.getNode());
- const string path = iterator.getNodePath();
+ const string casePath = iterator.getNodePath();
vk::SourceCollection progs;
- tcu::print("%s\n", path.c_str());
+ tcu::print("%s\n", casePath.c_str());
testCase->initPrograms(progs);
for (vk::SourceCollection::Iterator progIter = progs.begin(); progIter != progs.end(); ++progIter)
{
- tcu::print(" %s\n", progIter.getName().c_str());
+ try
+ {
+ const vk::ProgramIdentifier progId (casePath, progIter.getName());
+ const UniquePtr<vk::ProgramBinary> binary (vk::buildProgram(progIter.getProgram(), vk::PROGRAM_FORMAT_SPIRV));
- // \todo [2015-03-20 pyry] This is POC level, next steps:
- // - actually build programs
- // - eliminate duplicates
- // - store as binaries + name -> prog file map
+ if (mode == BUILDMODE_BUILD)
+ writer->storeProgram(progId, *binary);
+ else
+ {
+ DE_ASSERT(mode == BUILDMODE_VERIFY);
+
+ const UniquePtr<vk::ProgramBinary> storedBinary (reader->loadProgram(progId));
+
+ if (binary->getSize() != storedBinary->getSize())
+ throw tcu::Exception("Binary size doesn't match");
+
+ if (deMemCmp(binary->getBinary(), storedBinary->getBinary(), binary->getSize()))
+ throw tcu::Exception("Binary contents don't match");
+ }
+
+ tcu::print(" OK: %s\n", progIter.getName().c_str());
+ stats.numSucceeded += 1;
+ }
+ catch (const std::exception& e)
+ {
+ tcu::print(" ERROR: %s: %s\n", progIter.getName().c_str(), e.what());
+ stats.numFailed += 1;
+ }
}
}
iterator.next();
}
+
+ return stats;
}
} // vkt
+namespace opt
+{
+
+DE_DECLARE_COMMAND_LINE_OPT(DstPath, std::string);
+DE_DECLARE_COMMAND_LINE_OPT(Mode, vkt::BuildMode);
+
+} // opt
+
+void registerOptions (de::cmdline::Parser& parser)
+{
+ using de::cmdline::Option;
+ using de::cmdline::NamedValue;
+
+ static const NamedValue<vkt::BuildMode> s_modes[] =
+ {
+ { "build", vkt::BUILDMODE_BUILD },
+ { "verify", vkt::BUILDMODE_VERIFY }
+ };
+
+ parser << Option<opt::DstPath> ("d", "dst-path", "Destination path", ".")
+ << Option<opt::Mode> ("m", "mode", "Build mode", s_modes, "build");
+}
+
int main (int argc, const char* argv[])
{
+ de::cmdline::CommandLine cmdLine;
+
+ {
+ de::cmdline::Parser parser;
+ registerOptions(parser);
+ if (!parser.parse(argc, argv, &cmdLine, std::cerr))
+ {
+ parser.help(std::cout);
+ return -1;
+ }
+ }
+
try
{
- const tcu::CommandLine cmdLine (argc, argv);
- tcu::DirArchive archive (".");
- tcu::TestLog log (cmdLine.getLogFileName(), cmdLine.getLogFlags());
+ const tcu::CommandLine deqpCmdLine ("unused");
+ tcu::DirArchive archive (".");
+ tcu::TestLog log (deqpCmdLine.getLogFileName(), deqpCmdLine.getLogFlags());
tcu::Platform platform;
- tcu::TestContext testCtx (platform, archive, log, cmdLine, DE_NULL);
+ tcu::TestContext testCtx (platform, archive, log, deqpCmdLine, DE_NULL);
- vkt::buildPrograms(testCtx);
+ const vkt::BuildStats stats = vkt::buildPrograms(testCtx, cmdLine.getOption<opt::DstPath>(), cmdLine.getOption<opt::Mode>());
+
+ tcu::print("DONE: %d passed, %d failed\n", stats.numSucceeded, stats.numFailed);
+
+ return stats.numFailed == 0 ? 0 : -1;
}
catch (const std::exception& e)
{
tcu::die("%s", e.what());
}
-
- return 0;
}
diff --git a/modules/vulkan/vktTestPackage.cpp b/modules/vulkan/vktTestPackage.cpp
index a06687b..a690900 100644
--- a/modules/vulkan/vktTestPackage.cpp
+++ b/modules/vulkan/vktTestPackage.cpp
@@ -26,6 +26,7 @@
#include "tcuPlatform.hpp"
#include "vkPlatform.hpp"
#include "vkPrograms.hpp"
+#include "vkBinaryRegistry.hpp"
#include "deUniquePtr.hpp"
#include "vktInfo.hpp"
@@ -91,14 +92,28 @@
m_progCollection.clear();
vktCase->initPrograms(sourceProgs);
- // \todo [2015-03-13 pyry] Need abstraction for this - sometimes built on run-time, sometimes loaded from archive
for (vk::SourceCollection::Iterator progIter = sourceProgs.begin(); progIter != sourceProgs.end(); ++progIter)
{
- const std::string& name = progIter.getName();
- const glu::ProgramSources& srcProg = progIter.getProgram();
- de::MovePtr<vk::ProgramBinary> binProg = de::MovePtr<vk::ProgramBinary>(vk::buildProgram(srcProg, vk::PROGRAM_FORMAT_SPIRV));
+ const vk::ProgramIdentifier progId (casePath, progIter.getName());
+ de::MovePtr<vk::ProgramBinary> binProg;
- m_progCollection.add(name, binProg);
+ // \todo [2015-07-01 pyry] Command line parameter to control cache vs. build order?
+
+ try
+ {
+ binProg = de::MovePtr<vk::ProgramBinary>(vk::buildProgram(progIter.getProgram(), vk::PROGRAM_FORMAT_SPIRV));
+ }
+ catch (const tcu::NotSupportedError&)
+ {
+ // Try to load from cache
+ const vk::BinaryRegistryReader registry (m_context.getTestContext().getArchive(), "vulkan/prebuilt");
+
+ binProg = de::MovePtr<vk::ProgramBinary>(registry.loadProgram(progId));
+ }
+
+ TCU_CHECK_INTERNAL(binProg);
+
+ m_progCollection.add(progId.programName, binProg);
}
DE_ASSERT(!m_instance);
diff --git a/scripts/build_vulkan_programs.py b/scripts/build_vulkan_programs.py
new file mode 100644
index 0000000..e3b2b11
--- /dev/null
+++ b/scripts/build_vulkan_programs.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+
+#-------------------------------------------------------------------------
+# drawElements Quality Program utilities
+# --------------------------------------
+#
+# Copyright 2015 The Android Open Source Project
+#
+# 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.
+#
+#-------------------------------------------------------------------------
+
+from build.common import *
+from build.config import *
+from build.build import *
+
+import os
+import sys
+import string
+import argparse
+import tempfile
+import shutil
+import fnmatch
+
+class Module:
+ def __init__ (self, name, dirName, binName):
+ self.name = name
+ self.dirName = dirName
+ self.binName = binName
+
+VULKAN_MODULE = Module("dEQP-VK", "vulkan", "deqp-vk")
+DEFAULT_BUILD_DIR = os.path.join(tempfile.gettempdir(), "vulkan-programs", "{targetName}-{buildType}")
+DEFAULT_TARGET = "null"
+DEFAULT_DST_DIR = os.path.join(DEQP_DIR, "data", "vulkan", "prebuilt")
+
+def getBuildConfig (buildPathPtrn, targetName, buildType):
+ buildPath = buildPathPtrn.format(
+ targetName = targetName,
+ buildType = buildType)
+
+ return BuildConfig(buildPath, buildType, ["-DDEQP_TARGET=%s" % targetName])
+
+def cleanDstDir (dstPath):
+ binFiles = [f for f in os.listdir(dstPath) if os.path.isfile(os.path.join(dstPath, f)) and fnmatch.fnmatch(f, "*.spirv")]
+
+ for binFile in binFiles:
+ print "Removing %s" % os.path.join(dstPath, binFile)
+ os.remove(os.path.join(dstPath, binFile))
+
+def execBuildPrograms (buildCfg, generator, module, mode, dstPath):
+ workDir = os.path.join(buildCfg.getBuildDir(), "modules", module.dirName)
+
+ pushWorkingDir(workDir)
+
+ try:
+ binPath = generator.getBinaryPath(buildCfg.getBuildType(), os.path.join(".", "vk-build-programs"))
+ execute([binPath, "--mode", mode, "--dst-path", dstPath])
+ finally:
+ popWorkingDir()
+
+def parseArgs ():
+ parser = argparse.ArgumentParser(description = "Build SPIR-V programs",
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter)
+ parser.add_argument("-b",
+ "--build-dir",
+ dest="buildDir",
+ default=DEFAULT_BUILD_DIR,
+ help="Temporary build directory")
+ parser.add_argument("-t",
+ "--build-type",
+ dest="buildType",
+ default="Debug",
+ help="Build type")
+ parser.add_argument("-c",
+ "--deqp-target",
+ dest="targetName",
+ default=DEFAULT_TARGET,
+ help="dEQP build target")
+ parser.add_argument("--mode",
+ dest="mode",
+ default="build",
+ help="Build mode (build or verify)")
+ parser.add_argument("-d",
+ "--dst-path",
+ dest="dstPath",
+ default=DEFAULT_DST_DIR,
+ help="Destination path")
+ return parser.parse_args()
+
+if __name__ == "__main__":
+ args = parseArgs()
+
+ generator = ANY_GENERATOR
+ buildCfg = getBuildConfig(args.buildDir, args.targetName, args.buildType)
+ module = VULKAN_MODULE
+
+ build(buildCfg, generator, ["vk-build-programs"])
+
+ if args.mode == "build":
+ if os.path.exists(args.dstPath):
+ cleanDstDir(args.dstPath)
+ else:
+ os.makedirs(args.dstPath)
+
+ execBuildPrograms(buildCfg, generator, module, args.mode, args.dstPath)