spirv2dxil: Support linking multiple shaders

This probably could/should be split up into multiple commits, but
it's simpler to make this a monolithic change.

This change inlines a bunch of logic from spirv_to_dxil into the
spirv2dxil tool so that linking can be done on the nir shaders.
Probably the linking functionality should be exposed in the lib/dll
form too, which means that a helper for freeing intermediate nir
would be needed too. That's TODO for now.

The tool now requires arguments to be in-order, and once a filename
is encountered, will use the previous arguments to compile the shader.
If multiple graphics shaders are passed, they're linked as if they
were forming a pipeline together.

Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/20440>
This commit is contained in:
Jesse Natalie 2022-12-27 10:47:50 -08:00 committed by Marge Bot
parent ac7abf0483
commit 531d17c334
4 changed files with 210 additions and 190 deletions

View file

@ -1,56 +0,0 @@
/*
* Copyright © 2021 Intel Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#include <cstdio>
#include "dxil_validation.h"
#if DETECT_OS_WINDOWS
#include "dxil_validator.h"
bool
validate_dxil(dxil_spirv_object *dxil_obj)
{
struct dxil_validator *val = dxil_create_validator(NULL);
char *err;
bool res = dxil_validate_module(val, dxil_obj->binary.buffer,
dxil_obj->binary.size, &err);
if (!res && err)
fprintf(stderr, "DXIL: %s\n\n", err);
dxil_destroy_validator(val);
return res;
}
#else
bool
validate_dxil(dxil_spirv_object *dxil_obj)
{
fprintf(stderr, "DXIL validation only available in Windows.\n");
return false;
}
#endif

View file

@ -1,41 +0,0 @@
/*
* Copyright © 2021 Intel Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
#ifndef DXIL_VALIDATION_H
#define DXIL_VALIDATION_H
#include "spirv_to_dxil.h"
#include "util/detect_os.h"
#ifdef __cplusplus
extern "C" {
#endif
bool
validate_dxil(struct dxil_spirv_object *dxil_obj);
#ifdef __cplusplus
}
#endif
#endif /* DXIL_VALIDATION_H */

View file

@ -24,6 +24,7 @@ libspirv_to_dxil = both_libraries(
[
files(
'dxil_spirv_nir.c',
'dxil_spirv_nir.h',
'spirv_to_dxil.c',
'spirv_to_dxil.h',
),
@ -44,8 +45,6 @@ spirv_to_dxil = executable(
'spirv2dxil',
files(
'spirv2dxil.c',
'dxil_validation.h',
'dxil_validation.cpp',
),
dependencies : [idep_nir, idep_libdxil_compiler, idep_getopt, idep_libspirv_to_dxil],
include_directories : [inc_include, inc_src, inc_compiler, inc_gallium],

View file

@ -29,9 +29,10 @@
*/
#include "nir_to_dxil.h"
#include "dxil_validation.h"
#include "dxil_validator.h"
#include "spirv/nir_spirv.h"
#include "spirv_to_dxil.h"
#include "dxil_spirv_nir.h"
#include "util/os_file.h"
#include <errno.h>
@ -56,8 +57,6 @@ stage_to_enum(char *stage)
return MESA_SHADER_FRAGMENT;
else if (!strcmp(stage, "compute"))
return MESA_SHADER_COMPUTE;
else if (!strcmp(stage, "kernel"))
return MESA_SHADER_KERNEL;
else
return MESA_SHADER_NONE;
}
@ -68,73 +67,32 @@ log_spirv_to_dxil_error(void *priv, const char *msg)
fprintf(stderr, "spirv_to_dxil error: %s", msg);
}
int
main(int argc, char **argv)
struct shader {
const char *entry_point;
const char *output_file;
nir_shader *nir;
};
bool validate = false, debug = false;
enum dxil_validator_version val_ver = DXIL_VALIDATOR_1_4;
enum dxil_shader_model shader_model = SHADER_MODEL_6_2;
struct nir_shader_compiler_options nir_options;
static bool
compile_shader(const char *filename, gl_shader_stage shader_stage, struct shader *shader)
{
gl_shader_stage shader_stage = MESA_SHADER_FRAGMENT;
char *entry_point = "main";
char *output_file = "";
int ch;
bool validate = false, debug = false;
static struct option long_options[] = {
{"stage", required_argument, 0, 's'},
{"entry", required_argument, 0, 'e'},
{"output", required_argument, 0, 'o'},
{"validate", no_argument, 0, 'v'},
{"debug", no_argument, 0, 'd'},
{0, 0, 0, 0}};
while ((ch = getopt_long(argc, argv, "s:e:o:vd", long_options, NULL)) !=
-1) {
switch(ch)
{
case 's':
shader_stage = stage_to_enum(optarg);
if (shader_stage == MESA_SHADER_NONE) {
fprintf(stderr, "Unknown stage %s\n", optarg);
return 1;
}
break;
case 'e':
entry_point = optarg;
break;
case 'o':
output_file = optarg;
break;
case 'v':
validate = true;
break;
case 'd':
debug = true;
break;
default:
fprintf(stderr, "Unrecognized option.\n");
return 1;
}
}
if (optind != argc - 1) {
if (optind < argc)
fprintf(stderr, "Please specify only one input file.");
else
fprintf(stderr, "Please specify an input file.");
return 1;
}
const char *filename = argv[optind];
size_t file_size;
char *file_contents = os_read_file(filename, &file_size);
if (!file_contents) {
fprintf(stderr, "Failed to open %s\n", filename);
return 1;
return false;
}
if (file_size % WORD_SIZE != 0) {
fprintf(stderr, "%s size == %zu is not a multiple of %d\n", filename,
file_size, WORD_SIZE);
return 1;
return false;
}
size_t word_count = file_size / WORD_SIZE;
@ -145,46 +103,206 @@ main(int argc, char **argv)
conf.runtime_data_cbv.register_space = 31;
conf.zero_based_vertex_instance_id = true;
struct dxil_spirv_debug_options dbg_opts = {
.dump_nir = debug,
};
const struct dxil_spirv_logger logger = {
.priv = NULL,
.log = log_spirv_to_dxil_error
struct spirv_to_nir_options spirv_opts = {
.caps = {
.draw_parameters = true,
},
.ubo_addr_format = nir_address_format_32bit_index_offset,
.ssbo_addr_format = nir_address_format_32bit_index_offset,
.shared_addr_format = nir_address_format_32bit_offset_as_64bit,
// use_deref_buffer_array_length + nir_lower_explicit_io force
// get_ssbo_size to take in the return from load_vulkan_descriptor
// instead of vulkan_resource_index. This makes it much easier to
// get the DXIL handle for the SSBO.
.use_deref_buffer_array_length = true
};
struct dxil_spirv_object obj;
memset(&obj, 0, sizeof(obj));
if (spirv_to_dxil((uint32_t *)file_contents, word_count, NULL, 0,
(dxil_spirv_shader_stage)shader_stage, entry_point,
SHADER_MODEL_6_2, DXIL_VALIDATOR_1_4,
&dbg_opts, &conf, &logger, &obj)) {
shader->nir = spirv_to_nir(
(const uint32_t *)file_contents, word_count, NULL,
0, (gl_shader_stage)shader_stage, shader->entry_point,
&spirv_opts, &nir_options);
free(file_contents);
if (!shader->nir) {
fprintf(stderr, "SPIR-V to NIR failed\n");
return false;
}
if (validate && !validate_dxil(&obj)) {
fprintf(stderr, "Failed to validate DXIL\n");
spirv_to_dxil_free(&obj);
free(file_contents);
nir_validate_shader(shader->nir,
"Validate before feeding NIR to the DXIL compiler");
dxil_spirv_nir_prep(shader->nir);
bool requires_runtime_data;
dxil_spirv_nir_passes(shader->nir, &conf, &requires_runtime_data);
if (debug)
nir_print_shader(shader->nir, stderr);
return true;
}
#if DETECT_OS_WINDOWS
static bool
validate_dxil(struct blob *blob)
{
struct dxil_validator *val = dxil_create_validator(NULL);
char *err;
bool res = dxil_validate_module(val, blob->data,
blob->size, &err);
if (!res && err)
fprintf(stderr, "DXIL: %s\n\n", err);
dxil_destroy_validator(val);
return res;
}
#else
static bool
validate_dxil(struct blob *blob)
{
fprintf(stderr, "DXIL validation only available in Windows.\n");
return false;
}
#endif
int
main(int argc, char **argv)
{
glsl_type_singleton_init_or_ref();
int ch;
static struct option long_options[] = {
{"stage", required_argument, 0, 's'},
{"entry", required_argument, 0, 'e'},
{"output", required_argument, 0, 'o'},
{"validate", no_argument, 0, 'v'},
{"debug", no_argument, 0, 'd'},
{"shadermodel", required_argument, 0, 'm'},
{"validatorver", required_argument, 0, 'x'},
{0, 0, 0, 0}};
struct shader shaders[MESA_SHADER_COMPUTE + 1];
memset(shaders, 0, sizeof(shaders));
struct shader cur_shader = {
.entry_point = "main",
.output_file = "",
};
gl_shader_stage shader_stage = MESA_SHADER_FRAGMENT;
nir_options = *dxil_get_nir_compiler_options();
// We will manually handle base_vertex when vertex_id and instance_id have
// have been already converted to zero-base.
nir_options.lower_base_vertex = false;
bool any_shaders = false;
while ((ch = getopt_long(argc, argv, "-s:e:o:m:x:vd", long_options, NULL)) !=
-1) {
switch (ch)
{
case 's':
shader_stage = stage_to_enum(optarg);
if (shader_stage == MESA_SHADER_NONE) {
fprintf(stderr, "Unknown stage %s\n", optarg);
return 1;
}
break;
case 'e':
cur_shader.entry_point = optarg;
break;
case 'o':
cur_shader.output_file = optarg;
break;
case 'v':
validate = true;
break;
case 'd':
debug = true;
break;
case 'm':
shader_model = SHADER_MODEL_6_0 + atoi(optarg);
nir_options.lower_helper_invocation = shader_model < SHADER_MODEL_6_6;
break;
case 'x':
val_ver = DXIL_VALIDATOR_1_0 + atoi(optarg);
break;
case 1:
if (!compile_shader(optarg, shader_stage, &cur_shader))
return 1;
shaders[shader_stage] = cur_shader;
any_shaders = true;
break;
default:
fprintf(stderr, "Unrecognized option.\n");
return 1;
}
}
FILE *file = fopen(output_file, "wb");
if (!file) {
fprintf(stderr, "Failed to open %s, %s\n", output_file,
strerror(errno));
spirv_to_dxil_free(&obj);
free(file_contents);
return 1;
}
fwrite(obj.binary.buffer, sizeof(char), obj.binary.size, file);
fclose(file);
spirv_to_dxil_free(&obj);
} else {
fprintf(stderr, "Compilation failed\n");
if (!any_shaders) {
fprintf(stderr, "Specify a shader filename\n");
return 1;
}
free(file_contents);
for (int32_t cur = MESA_SHADER_FRAGMENT; cur >= MESA_SHADER_VERTEX; --cur) {
if (!shaders[cur].nir)
continue;
for (int32_t prev = cur - 1; prev >= MESA_SHADER_VERTEX; --prev) {
if (!shaders[prev].nir)
continue;
dxil_spirv_nir_link(shaders[cur].nir, shaders[prev].nir);
break;
}
}
struct nir_to_dxil_options opts = {
.environment = DXIL_ENVIRONMENT_VULKAN,
.shader_model_max = shader_model,
.validator_version_max = val_ver,
};
struct dxil_logger logger_inner = {.priv = NULL,
.log = log_spirv_to_dxil_error};
for (uint32_t i = 0; i <= MESA_SHADER_COMPUTE; ++i) {
if (!shaders[i].nir)
continue;
struct blob dxil_blob;
bool success = nir_to_dxil(shaders[i].nir, &opts, &logger_inner, &dxil_blob);
ralloc_free(shaders[i].nir);
if (!success) {
fprintf(stderr, "Failed to convert to DXIL\n");
if (dxil_blob.allocated)
blob_finish(&dxil_blob);
return false;
}
if (validate && !validate_dxil(&dxil_blob)) {
fprintf(stderr, "Failed to validate DXIL\n");
blob_finish(&dxil_blob);
return 1;
}
if (shaders[i].output_file) {
FILE *file = fopen(shaders[i].output_file, "wb");
if (!file) {
fprintf(stderr, "Failed to open %s, %s\n", shaders[i].output_file,
strerror(errno));
blob_finish(&dxil_blob);
return 1;
}
fwrite(dxil_blob.data, sizeof(char), dxil_blob.size, file);
fclose(file);
blob_finish(&dxil_blob);
}
}
glsl_type_singleton_decref();
return 0;
}