1/*-------------------------------------------------------------------------
2 * Vulkan Conformance Tests
3 * ------------------------
4 *
5 * Copyright (c) 2015 Google Inc.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *      http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 *//*!
20 * \file
21 * \brief Utility for pre-compiling source programs to SPIR-V
22 *//*--------------------------------------------------------------------*/
23
24#include "tcuDefs.hpp"
25#include "tcuCommandLine.hpp"
26#include "tcuPlatform.hpp"
27#include "tcuResource.hpp"
28#include "tcuTestLog.hpp"
29#include "tcuTestHierarchyIterator.hpp"
30#include "deUniquePtr.hpp"
31#include "vkPrograms.hpp"
32#include "vkBinaryRegistry.hpp"
33#include "vktTestCase.hpp"
34#include "vktTestPackage.hpp"
35#include "deUniquePtr.hpp"
36#include "deCommandLine.hpp"
37#include "deSharedPtr.hpp"
38
39#include <iostream>
40
41using std::vector;
42using std::string;
43using de::UniquePtr;
44using de::MovePtr;
45using de::SharedPtr;
46
47namespace vkt
48{
49
50tcu::TestPackageRoot* createRoot (tcu::TestContext& testCtx)
51{
52	vector<tcu::TestNode*>	children;
53	children.push_back(new TestPackage(testCtx));
54	return new tcu::TestPackageRoot(testCtx, children);
55}
56
57struct BuildStats
58{
59	int		numSucceeded;
60	int		numFailed;
61
62	BuildStats (void)
63		: numSucceeded	(0)
64		, numFailed		(0)
65	{
66	}
67};
68
69namespace // anonymous
70{
71
72void writeBuildLogs (const glu::ShaderProgramInfo& buildInfo, std::ostream& dst)
73{
74	for (size_t shaderNdx = 0; shaderNdx < buildInfo.shaders.size(); shaderNdx++)
75	{
76		const glu::ShaderInfo&	shaderInfo	= buildInfo.shaders[shaderNdx];
77		const char* const		shaderName	= getShaderTypeName(shaderInfo.type);
78
79		dst << shaderName << " source:\n"
80			<< "---\n"
81			<< shaderInfo.source << "\n"
82			<< "---\n"
83			<< shaderName << " compile log:\n"
84			<< "---\n"
85			<< shaderInfo.infoLog << "\n"
86			<< "---\n";
87	}
88
89	dst << "link log:\n"
90		<< "---\n"
91		<< buildInfo.program.infoLog << "\n"
92		<< "---\n";
93}
94
95void writeBuildLogs (const vk::SpirVProgramInfo& buildInfo, std::ostream& dst)
96{
97	dst << "source:\n"
98		<< "---\n"
99		<< buildInfo.source << "\n"
100		<< "---\n";
101}
102
103vk::ProgramBinary* compileProgram (const glu::ProgramSources& source, std::ostream& buildLog)
104{
105	glu::ShaderProgramInfo	buildInfo;
106
107	try
108	{
109		return vk::buildProgram(source, vk::PROGRAM_FORMAT_SPIRV, &buildInfo);
110	}
111	catch (const tcu::Exception&)
112	{
113		writeBuildLogs(buildInfo, buildLog);
114		throw;
115	}
116}
117
118vk::ProgramBinary* compileProgram (const vk::SpirVAsmSource& source, std::ostream& buildLog)
119{
120	vk::SpirVProgramInfo	buildInfo;
121
122	try
123	{
124		return vk::assembleProgram(source, &buildInfo);
125	}
126	catch (const tcu::Exception&)
127	{
128		writeBuildLogs(buildInfo, buildLog);
129		throw;
130	}
131}
132
133struct BuiltProgram
134{
135	vk::ProgramIdentifier			id;
136	bool							buildOk;
137	UniquePtr<vk::ProgramBinary>	binary;		// Null if build failed
138	std::string						buildLog;
139
140	BuiltProgram (const vk::ProgramIdentifier&	id_,
141				  bool							buildOk_,
142				  MovePtr<vk::ProgramBinary>	binary_,
143				  const std::string&			buildLog_)
144		: id		(id_)
145		, buildOk	(buildOk_)
146		, binary	(binary_)
147		, buildLog	(buildLog_)
148	{
149	}
150};
151
152typedef SharedPtr<BuiltProgram> BuiltProgramSp;
153
154template<typename IteratorType>
155BuiltProgramSp buildProgram (IteratorType progIter, const std::string& casePath)
156{
157	std::ostringstream			buildLog;
158	MovePtr<vk::ProgramBinary>	programBinary;
159	bool						buildOk			= false;
160
161	try
162	{
163		programBinary	= MovePtr<vk::ProgramBinary>(compileProgram(progIter.getProgram(), buildLog));
164		buildOk			= true;
165	}
166	catch (const std::exception&)
167	{
168		// Ignore, buildOk = false
169		DE_ASSERT(!programBinary);
170	}
171
172	return BuiltProgramSp(new BuiltProgram(vk::ProgramIdentifier(casePath, progIter.getName()),
173										   buildOk,
174										   programBinary,
175										   buildLog.str()));
176}
177
178} // anonymous
179
180BuildStats buildPrograms (tcu::TestContext& testCtx, const std::string& dstPath, bool validateBinaries)
181{
182	const UniquePtr<tcu::TestPackageRoot>	root		(createRoot(testCtx));
183	tcu::DefaultHierarchyInflater			inflater	(testCtx);
184	tcu::TestHierarchyIterator				iterator	(*root, inflater, testCtx.getCommandLine());
185	const tcu::DirArchive					srcArchive	(dstPath.c_str());
186	UniquePtr<vk::BinaryRegistryWriter>		writer		(new vk::BinaryRegistryWriter(dstPath));
187	BuildStats								stats;
188
189	while (iterator.getState() != tcu::TestHierarchyIterator::STATE_FINISHED)
190	{
191		if (iterator.getState() == tcu::TestHierarchyIterator::STATE_ENTER_NODE &&
192			tcu::isTestNodeTypeExecutable(iterator.getNode()->getNodeType()))
193		{
194			const TestCase* const		testCase	= dynamic_cast<TestCase*>(iterator.getNode());
195			const string				casePath	= iterator.getNodePath();
196			vk::SourceCollections		sourcePrograms;
197			vector<BuiltProgramSp>		builtPrograms;
198
199			tcu::print("%s\n", casePath.c_str());
200
201			testCase->initPrograms(sourcePrograms);
202
203			for (vk::GlslSourceCollection::Iterator progIter = sourcePrograms.glslSources.begin();
204				 progIter != sourcePrograms.glslSources.end();
205				 ++progIter)
206			{
207				builtPrograms.push_back(buildProgram(progIter, casePath));
208			}
209
210			for (vk::SpirVAsmCollection::Iterator progIter = sourcePrograms.spirvAsmSources.begin();
211				 progIter != sourcePrograms.spirvAsmSources.end();
212				 ++progIter)
213			{
214				builtPrograms.push_back(buildProgram(progIter, casePath));
215			}
216
217			// Process programs
218			for (vector<BuiltProgramSp>::const_iterator progIter = builtPrograms.begin();
219				 progIter != builtPrograms.end();
220				 ++progIter)
221			{
222				const BuiltProgram&	program	= **progIter;
223
224				if (program.buildOk)
225				{
226					std::ostringstream	validationLog;
227
228					writer->storeProgram(program.id, *program.binary);
229
230					if (validateBinaries &&
231						!vk::validateProgram(*program.binary, &validationLog))
232					{
233						tcu::print("ERROR: validation failed for %s\n", program.id.programName.c_str());
234						tcu::print("%s\n", validationLog.str().c_str());
235						stats.numFailed += 1;
236					}
237					else
238						stats.numSucceeded += 1;
239				}
240				else
241				{
242					tcu::print("ERROR: failed to build %s\n", program.id.programName.c_str());
243					tcu::print("%s\n", program.buildLog.c_str());
244					stats.numFailed += 1;
245				}
246			}
247		}
248
249		iterator.next();
250	}
251
252	writer->writeIndex();
253
254	return stats;
255}
256
257} // vkt
258
259namespace opt
260{
261
262DE_DECLARE_COMMAND_LINE_OPT(DstPath,	std::string);
263DE_DECLARE_COMMAND_LINE_OPT(Cases,		std::string);
264DE_DECLARE_COMMAND_LINE_OPT(Validate,	bool);
265
266} // opt
267
268void registerOptions (de::cmdline::Parser& parser)
269{
270	using de::cmdline::Option;
271
272	parser << Option<opt::DstPath>	("d", "dst-path",		"Destination path",	"out")
273		   << Option<opt::Cases>	("n", "deqp-case",		"Case path filter (works as in test binaries)")
274		   << Option<opt::Validate>	("v", "validate-spv",	"Validate generated SPIR-V binaries");
275}
276
277int main (int argc, const char* argv[])
278{
279	de::cmdline::CommandLine	cmdLine;
280	tcu::CommandLine			deqpCmdLine;
281
282	{
283		de::cmdline::Parser		parser;
284		registerOptions(parser);
285		if (!parser.parse(argc, argv, &cmdLine, std::cerr))
286		{
287			parser.help(std::cout);
288			return -1;
289		}
290	}
291
292	{
293		vector<const char*> deqpArgv;
294
295		deqpArgv.push_back("unused");
296
297		if (cmdLine.hasOption<opt::Cases>())
298		{
299			deqpArgv.push_back("--deqp-case");
300			deqpArgv.push_back(cmdLine.getOption<opt::Cases>().c_str());
301		}
302
303		if (!deqpCmdLine.parse((int)deqpArgv.size(), &deqpArgv[0]))
304			return -1;
305	}
306
307	try
308	{
309		tcu::DirArchive			archive			(".");
310		tcu::TestLog			log				(deqpCmdLine.getLogFileName(), deqpCmdLine.getLogFlags());
311		tcu::Platform			platform;
312		tcu::TestContext		testCtx			(platform, archive, log, deqpCmdLine, DE_NULL);
313
314		const vkt::BuildStats	stats			= vkt::buildPrograms(testCtx,
315																	 cmdLine.getOption<opt::DstPath>(),
316																	 cmdLine.getOption<opt::Validate>());
317
318		tcu::print("DONE: %d passed, %d failed\n", stats.numSucceeded, stats.numFailed);
319
320		return stats.numFailed == 0 ? 0 : -1;
321	}
322	catch (const std::exception& e)
323	{
324		tcu::die("%s", e.what());
325	}
326}
327