vkd3d/tests/shader_runner_gl.c
2024-08-29 12:08:46 +02:00

1342 lines
45 KiB
C

/*
* Copyright 2023 Henri Verbeet for CodeWeavers
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "config.h"
#ifdef HAVE_OPENGL
#ifndef __MINGW32__
#define WIDL_C_INLINE_WRAPPERS
#endif
#define COBJMACROS
#define VKD3D_TEST_NO_DEFS
#define GL_GLEXT_PROTOTYPES
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GL/gl.h>
#include "shader_runner.h"
#include "vkd3d_d3dcompiler.h"
static PFNGLSPECIALIZESHADERPROC p_glSpecializeShader;
enum shading_language
{
GLSL,
SPIR_V,
};
struct format_info
{
enum DXGI_FORMAT f;
unsigned int component_count;
bool is_integer;
bool is_shadow;
GLenum internal_format;
GLenum format;
GLenum type;
};
struct gl_resource
{
struct resource r;
const struct format_info *format;
GLuint id, tbo_id;
};
static struct gl_resource *gl_resource(struct resource *r)
{
return CONTAINING_RECORD(r, struct gl_resource, r);
}
struct gl_runner
{
struct shader_runner r;
struct shader_runner_caps caps;
EGLDisplay display;
EGLContext context;
uint32_t attribute_map;
GLuint fbo_id;
struct vkd3d_shader_combined_resource_sampler *combined_samplers;
unsigned int combined_sampler_count;
enum shading_language language;
};
static struct gl_runner *gl_runner(struct shader_runner *r)
{
return CONTAINING_RECORD(r, struct gl_runner, r);
}
static void debug_output(GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar *message, const void *userParam)
{
if (message[length - 1] == '\n')
--length;
trace("%.*s\n", length, message);
}
static bool check_gl_extension(const char *extension, GLint extension_count)
{
for (GLint i = 0; i < extension_count; ++i)
{
if (!strcmp(extension, (const char *)glGetStringi(GL_EXTENSIONS, i)))
return true;
}
return false;
}
static bool check_gl_extensions(struct gl_runner *runner)
{
GLint count;
static const char *required_extensions[] =
{
"GL_ARB_clip_control",
"GL_ARB_compute_shader",
"GL_ARB_sampler_objects",
"GL_ARB_shader_image_load_store",
"GL_ARB_texture_storage",
"GL_ARB_internalformat_query",
};
glGetIntegerv(GL_NUM_EXTENSIONS, &count);
if (runner->language == SPIR_V && !check_gl_extension("GL_ARB_gl_spirv", count))
return false;
for (unsigned int i = 0; i < ARRAY_SIZE(required_extensions); ++i)
{
if (!check_gl_extension(required_extensions[i], count))
return false;
}
if (check_gl_extension("GL_ARB_gpu_shader_fp64", count))
runner->caps.float64 = true;
if (check_gl_extension("GL_ARB_gpu_shader_int64", count))
runner->caps.int64 = true;
return true;
}
static bool check_egl_client_extension(const char *extension)
{
const char *extensions, *p;
size_t len;
if (!(extensions = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS)))
return false;
len = strlen(extension);
for (;;)
{
if (!(p = strchr(extensions, ' ')))
p = &extensions[strlen(extensions)];
if (p - extensions == len && !memcmp(extensions, extension, len))
return true;
if (!*p)
break;
extensions = p + 1;
}
return false;
}
static const struct format_info *get_format_info(enum DXGI_FORMAT format, bool is_shadow)
{
size_t i;
static const struct format_info format_info[] =
{
{DXGI_FORMAT_UNKNOWN, 1, true, false, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT},
{DXGI_FORMAT_R32G32B32A32_FLOAT, 4, false, false, GL_RGBA32F, GL_RGBA, GL_FLOAT},
{DXGI_FORMAT_R32G32B32A32_UINT, 4, true, false, GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT},
{DXGI_FORMAT_R32G32B32A32_SINT, 4, true, false, GL_RGBA32I, GL_RGBA_INTEGER, GL_INT},
{DXGI_FORMAT_R32G32_FLOAT, 2, false, false, GL_RG32F, GL_RG, GL_FLOAT},
{DXGI_FORMAT_R32G32_UINT, 2, true, false, GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT},
{DXGI_FORMAT_R32G32_SINT, 2, true, false, GL_RG32I, GL_RG_INTEGER, GL_INT},
{DXGI_FORMAT_R32_FLOAT, 1, false, false, GL_R32F, GL_RED, GL_FLOAT},
{DXGI_FORMAT_R32_FLOAT, 1, false, true, GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT},
{DXGI_FORMAT_D32_FLOAT, 1, false, true, GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT},
{DXGI_FORMAT_R32_UINT, 1, true, false, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT},
{DXGI_FORMAT_R32_SINT, 1, true, false, GL_R32I, GL_RED_INTEGER, GL_INT},
{DXGI_FORMAT_R32_TYPELESS, 1, true, false, GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT},
};
for (i = 0; i < ARRAY_SIZE(format_info); ++i)
{
if (format_info[i].f == format && format_info[i].is_shadow == is_shadow)
return &format_info[i];
}
fatal_error("Failed to find format info for format %#x.\n", format);
}
static uint32_t get_format_support(struct gl_runner *runner, enum DXGI_FORMAT format)
{
GLenum gl_format = get_format_info(format, false)->internal_format;
uint32_t ret = 0;
GLint support;
/* TODO: Probably check for more targets instead of just GL_TEXTURE_2D. */
glGetInternalformativ(GL_TEXTURE_2D, gl_format, GL_SHADER_IMAGE_LOAD, 1, &support);
if (support != GL_NONE)
ret |= FORMAT_CAP_UAV_LOAD;
return ret;
}
static bool gl_runner_init(struct gl_runner *runner, enum shading_language language)
{
PFNEGLQUERYDEVICESEXTPROC eglQueryDevicesEXT;
const char *glsl_version = NULL;
EGLint count, extension_count;
EGLDeviceEXT *devices;
EGLContext context;
EGLDisplay display;
EGLBoolean ret;
GLuint vao;
static const char *const tags[] =
{
"glsl",
};
static const EGLint attributes[] =
{
EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
EGL_CONTEXT_FLAGS_KHR, EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR,
EGL_CONTEXT_MAJOR_VERSION, 3,
EGL_CONTEXT_MINOR_VERSION, 2,
EGL_NONE,
};
static const enum DXGI_FORMAT formats[] =
{
DXGI_FORMAT_UNKNOWN,
DXGI_FORMAT_R32_FLOAT,
DXGI_FORMAT_R32_UINT,
DXGI_FORMAT_R32_SINT,
DXGI_FORMAT_R32G32B32A32_FLOAT,
DXGI_FORMAT_R32G32B32A32_UINT,
DXGI_FORMAT_R32G32B32A32_SINT,
};
memset(runner, 0, sizeof(*runner));
runner->language = language;
if (!check_egl_client_extension("EGL_EXT_device_enumeration")
|| !(eglQueryDevicesEXT = (void *)eglGetProcAddress("eglQueryDevicesEXT")))
{
skip("Failed to retrieve eglQueryDevicesEXT.\n");
return false;
}
ret = eglQueryDevicesEXT(0, NULL, &count);
ok(ret, "Failed to query device count.\n");
devices = calloc(count, sizeof(*devices));
ret = eglQueryDevicesEXT(count, devices, &count);
ok(ret, "Failed to query devices.\n");
for (unsigned int i = 0; i < count; ++i)
{
if ((display = eglGetPlatformDisplay(EGL_PLATFORM_DEVICE_EXT, devices[i], NULL)) == EGL_NO_DISPLAY)
{
trace("Failed to get EGL display connection for device %u.\n", i);
continue;
}
if (!eglInitialize(display, NULL, NULL))
{
trace("Failed to initialise EGL display connection for device %u.\n", i);
continue;
}
if (!eglBindAPI(EGL_OPENGL_API))
{
trace("Failed to bind OpenGL API for device %u.\n", i);
eglTerminate(display);
continue;
}
if ((context = eglCreateContext(display, NULL, EGL_NO_CONTEXT, attributes)) == EGL_NO_CONTEXT)
{
trace("Failed to create EGL context for device %u.\n", i);
eglTerminate(display);
continue;
}
if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, context))
{
trace("Failed to make EGL context current for device %u.\n", i);
eglDestroyContext(display, context);
eglTerminate(display);
continue;
}
glsl_version = (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION);
if (language == GLSL)
{
unsigned int major, minor;
sscanf(glsl_version, "%u.%u", &major, &minor);
if (major < 4 || (major == 4 && minor < 40))
{
trace("Device %u does not support GLSL 4.40.\n", i);
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(display, context);
eglTerminate(display);
continue;
}
}
memset(&runner->caps, 0, sizeof(runner->caps));
if (!check_gl_extensions(runner))
{
trace("Device %u lacks required extensions.\n", i);
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(display, context);
eglTerminate(display);
continue;
}
runner->caps.runner = language == SPIR_V ? "OpenGL/SPIR-V" : "OpenGL/GLSL";
runner->caps.tags = tags;
runner->caps.tag_count = runner->language == GLSL;
runner->caps.minimum_shader_model = SHADER_MODEL_4_0;
runner->caps.maximum_shader_model = SHADER_MODEL_5_1;
glGetIntegerv(GL_NUM_EXTENSIONS, &extension_count);
if (check_gl_extension("GL_ARB_internalformat_query2", extension_count))
{
for (unsigned int j = 0; j < ARRAY_SIZE(formats); ++j)
{
runner->caps.format_caps[formats[j]] = get_format_support(runner, formats[j]);
}
}
trace("Using device %u.\n", i);
runner->display = display;
runner->context = context;
break;
}
free(devices);
if (!runner->context)
{
skip("Failed to find a usable OpenGL device.\n");
return false;
}
trace(" GL_VENDOR: %s\n", glGetString(GL_VENDOR));
trace(" GL_RENDERER: %s\n", glGetString(GL_RENDERER));
trace(" GL_VERSION: %s\n", glGetString(GL_VERSION));
trace("GL_SHADING_LANGUAGE_VERSION: %s\n", glsl_version);
p_glSpecializeShader = (void *)eglGetProcAddress("glSpecializeShader");
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION, 0, NULL, GL_FALSE);
glDebugMessageCallback(debug_output, NULL);
glClipControl(GL_UPPER_LEFT, GL_ZERO_TO_ONE);
glFrontFace(GL_CW);
glProvokingVertex(GL_FIRST_VERTEX_CONVENTION);
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
return true;
}
static void reset_combined_samplers(struct gl_runner *runner)
{
free(runner->combined_samplers);
runner->combined_samplers = NULL;
runner->combined_sampler_count = 0;
}
static void gl_runner_cleanup(struct gl_runner *runner)
{
EGLBoolean ret;
reset_combined_samplers(runner);
if (runner->fbo_id)
glDeleteFramebuffers(1, &runner->fbo_id);
ret = eglMakeCurrent(runner->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
ok(ret, "Failed to release current EGL context.\n");
ret = eglDestroyContext(runner->display, runner->context);
ok(ret, "Failed to destroy EGL context.\n");
ret = eglTerminate(runner->display);
ok(ret, "Failed to terminate EGL display connection.\n");
}
static bool init_resource_2d(struct gl_resource *resource, const struct resource_params *params)
{
GLenum target = params->desc.sample_count > 1 ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
unsigned int offset, w, h, i;
resource->format = get_format_info(params->desc.format, params->is_shadow);
if (params->desc.sample_count > 1)
{
GLint max_sample_count;
glGetInternalformativ(GL_TEXTURE_2D_MULTISAMPLE, resource->format->internal_format, GL_SAMPLES, 1, &max_sample_count);
if (max_sample_count < params->desc.sample_count)
{
trace("Format #%x with sample count %u is not supported; skipping.\n", params->desc.format, params->desc.sample_count);
return false;
}
}
glGenTextures(1, &resource->id);
glBindTexture(target, resource->id);
if (params->desc.sample_count > 1)
{
glTexStorage2DMultisample(target, params->desc.sample_count,
resource->format->internal_format, params->desc.width, params->desc.height, GL_FALSE);
}
else
{
glTexStorage2D(target, params->desc.level_count,
resource->format->internal_format, params->desc.width, params->desc.height);
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
}
if (!params->data)
return true;
for (i = 0, offset = 0; i < params->desc.level_count; ++i)
{
w = get_level_dimension(params->desc.width, i);
h = get_level_dimension(params->desc.height, i);
glTexSubImage2D(target, i, 0, 0, w, h, resource->format->format,
resource->format->type, params->data + offset);
offset += w * h * params->desc.texel_size;
}
return true;
}
static void init_resource_buffer(struct gl_resource *resource, const struct resource_params *params)
{
resource->format = get_format_info(params->desc.format, false);
glGenBuffers(1, &resource->id);
glBindBuffer(GL_TEXTURE_BUFFER, resource->id);
glBufferData(GL_TEXTURE_BUFFER, params->data_size, params->data, GL_STATIC_DRAW);
glGenTextures(1, &resource->tbo_id);
glBindTexture(GL_TEXTURE_BUFFER, resource->tbo_id);
glTexBuffer(GL_TEXTURE_BUFFER, resource->format->internal_format, resource->id);
}
static struct resource *gl_runner_create_resource(struct shader_runner *r, const struct resource_params *params)
{
struct gl_resource *resource;
resource = calloc(1, sizeof(*resource));
init_resource(&resource->r, params);
switch (params->desc.type)
{
case RESOURCE_TYPE_RENDER_TARGET:
case RESOURCE_TYPE_DEPTH_STENCIL:
case RESOURCE_TYPE_TEXTURE:
case RESOURCE_TYPE_UAV:
if (params->desc.dimension == RESOURCE_DIMENSION_BUFFER)
init_resource_buffer(resource, params);
else if (!init_resource_2d(resource, params))
return NULL;
break;
case RESOURCE_TYPE_VERTEX_BUFFER:
glGenBuffers(1, &resource->id);
glBindBuffer(GL_ARRAY_BUFFER, resource->id);
glBufferData(GL_ARRAY_BUFFER, params->data_size, params->data, GL_STATIC_DRAW);
break;
}
return &resource->r;
}
static void gl_runner_destroy_resource(struct shader_runner *r, struct resource *res)
{
struct gl_resource *resource = gl_resource(res);
switch (resource->r.desc.type)
{
case RESOURCE_TYPE_RENDER_TARGET:
case RESOURCE_TYPE_DEPTH_STENCIL:
case RESOURCE_TYPE_TEXTURE:
case RESOURCE_TYPE_UAV:
if (res->desc.dimension == RESOURCE_DIMENSION_BUFFER)
{
glDeleteTextures(1, &resource->tbo_id);
glDeleteBuffers(1, &resource->id);
}
else
{
glDeleteTextures(1, &resource->id);
}
break;
case RESOURCE_TYPE_VERTEX_BUFFER:
glDeleteBuffers(1, &resource->id);
break;
}
free(resource);
}
static ID3DBlob *compile_hlsl(const struct shader_runner *runner, const char *source, const char *type)
{
ID3DBlob *blob = NULL, *errors = NULL;
char profile[7];
static const char *const shader_models[] =
{
[SHADER_MODEL_4_0] = "4_0",
[SHADER_MODEL_4_1] = "4_1",
[SHADER_MODEL_5_0] = "5_0",
[SHADER_MODEL_5_1] = "5_1",
};
sprintf(profile, "%s_%s", type, shader_models[runner->minimum_shader_model]);
D3DCompile(source, strlen(source), NULL, NULL, NULL, "main",
profile, runner->compile_options, 0, &blob, &errors);
if (errors)
{
trace("%s\n", (char *)ID3D10Blob_GetBufferPointer(errors));
ID3D10Blob_Release(errors);
}
return blob;
}
static bool compile_shader(struct gl_runner *runner, ID3DBlob *blob, struct vkd3d_shader_code *out)
{
struct vkd3d_shader_spirv_target_info spirv_info = {.type = VKD3D_SHADER_STRUCTURE_TYPE_SPIRV_TARGET_INFO};
struct vkd3d_shader_interface_info interface_info = {.type = VKD3D_SHADER_STRUCTURE_TYPE_INTERFACE_INFO};
struct vkd3d_shader_compile_info info = {.type = VKD3D_SHADER_STRUCTURE_TYPE_COMPILE_INFO};
struct vkd3d_shader_resource_binding bindings[MAX_RESOURCES + 1 /* CBV */];
struct vkd3d_shader_scan_combined_resource_sampler_info combined_sampler_info;
struct vkd3d_shader_combined_resource_sampler *sampler;
struct vkd3d_shader_resource_binding *binding;
struct vkd3d_shader_parameter parameters[1];
unsigned int count, i;
char *messages;
int ret;
const struct vkd3d_shader_compile_option options[] =
{
{VKD3D_SHADER_COMPILE_OPTION_API_VERSION, VKD3D_SHADER_API_VERSION_1_13},
{VKD3D_SHADER_COMPILE_OPTION_FRAGMENT_COORDINATE_ORIGIN,
VKD3D_SHADER_COMPILE_OPTION_FRAGMENT_COORDINATE_ORIGIN_LOWER_LEFT},
{VKD3D_SHADER_COMPILE_OPTION_FEATURE, shader_runner_caps_get_feature_flags(&runner->caps)},
};
info.next = &combined_sampler_info;
info.source.code = ID3D10Blob_GetBufferPointer(blob);
info.source.size = ID3D10Blob_GetBufferSize(blob);
info.source_type = VKD3D_SHADER_SOURCE_DXBC_TPF;
info.target_type = runner->language == SPIR_V ? VKD3D_SHADER_TARGET_SPIRV_BINARY : VKD3D_SHADER_TARGET_GLSL;
info.options = options;
info.option_count = ARRAY_SIZE(options);
info.log_level = VKD3D_SHADER_LOG_WARNING;
combined_sampler_info.type = VKD3D_SHADER_STRUCTURE_TYPE_SCAN_COMBINED_RESOURCE_SAMPLER_INFO;
combined_sampler_info.next = NULL;
ret = vkd3d_shader_scan(&info, &messages);
if (messages && vkd3d_test_state.debug_level)
trace("%s\n", messages);
vkd3d_shader_free_messages(messages);
if (ret)
return false;
count = runner->combined_sampler_count + combined_sampler_info.combined_sampler_count;
if (count && !(runner->combined_samplers = realloc(runner->combined_samplers,
count * sizeof(*runner->combined_samplers))))
fatal_error("Failed to allocate combined samplers array.\n");
for (i = 0; i < combined_sampler_info.combined_sampler_count; ++i)
{
const struct vkd3d_shader_combined_resource_sampler_info *s = &combined_sampler_info.combined_samplers[i];
sampler = &runner->combined_samplers[runner->combined_sampler_count];
sampler->resource_space = s->resource_space;
sampler->resource_index = s->resource_index;
sampler->sampler_space = s->sampler_space;
sampler->sampler_index = s->sampler_index;
sampler->shader_visibility = VKD3D_SHADER_VISIBILITY_ALL;
/* We don't know if this combined sampler was created from a SRV buffer or a SRV image, so
* we pass both flags, otherwise the combined sampler won't be recognized when emitting the
* SPIR-V, which will result in a failing assertion. */
sampler->flags = VKD3D_SHADER_BINDING_FLAG_IMAGE | VKD3D_SHADER_BINDING_FLAG_BUFFER;
sampler->binding.set = 0;
sampler->binding.binding = runner->combined_sampler_count++;
sampler->binding.count = 1;
}
vkd3d_shader_free_scan_combined_resource_sampler_info(&combined_sampler_info);
if (runner->language == SPIR_V)
{
info.next = &spirv_info;
spirv_info.next = &interface_info;
spirv_info.environment = VKD3D_SHADER_SPIRV_ENVIRONMENT_OPENGL_4_5;
}
else
{
info.next = &interface_info;
}
if (runner->r.uniform_count)
{
binding = &bindings[interface_info.binding_count++];
binding->type = VKD3D_SHADER_DESCRIPTOR_TYPE_CBV;
binding->register_space = 0;
binding->register_index = 0;
binding->shader_visibility = VKD3D_SHADER_VISIBILITY_ALL;
binding->flags = VKD3D_SHADER_BINDING_FLAG_BUFFER;
binding->binding.set = 0;
binding->binding.binding = 0;
binding->binding.count = 1;
}
for (i = 0; i < runner->r.resource_count; ++i)
{
const struct gl_resource *resource = gl_resource(runner->r.resources[i]);
switch (resource->r.desc.type)
{
case RESOURCE_TYPE_UAV:
binding = &bindings[interface_info.binding_count++];
binding->type = VKD3D_SHADER_DESCRIPTOR_TYPE_UAV;
binding->register_space = 0;
binding->register_index = resource->r.desc.slot;
binding->shader_visibility = VKD3D_SHADER_VISIBILITY_ALL;
if (resource->r.desc.dimension == RESOURCE_DIMENSION_BUFFER)
binding->flags = VKD3D_SHADER_BINDING_FLAG_BUFFER;
else
binding->flags = VKD3D_SHADER_BINDING_FLAG_IMAGE;
binding->binding.set = 0;
binding->binding.binding = resource->r.desc.slot;
binding->binding.count = 1;
break;
default:
break;
}
}
interface_info.bindings = bindings;
interface_info.combined_samplers = runner->combined_samplers;
interface_info.combined_sampler_count = runner->combined_sampler_count;
parameters[0].name = VKD3D_SHADER_PARAMETER_NAME_RASTERIZER_SAMPLE_COUNT;
parameters[0].type = VKD3D_SHADER_PARAMETER_TYPE_IMMEDIATE_CONSTANT;
parameters[0].data_type = VKD3D_SHADER_PARAMETER_DATA_TYPE_UINT32;
parameters[0].u.immediate_constant.u.u32 = runner->r.sample_count;
spirv_info.parameter_count = ARRAY_SIZE(parameters);
spirv_info.parameters = parameters;
ret = vkd3d_shader_compile(&info, out, &messages);
if (messages && vkd3d_test_state.debug_level)
trace("%s\n", messages);
vkd3d_shader_free_messages(messages);
if (ret)
return false;
return true;
}
static void trace_info_log(GLuint id, bool program)
{
const char *p, *end, *line;
GLint length = 0;
char *log;
if (program)
glGetProgramiv(id, GL_INFO_LOG_LENGTH, &length);
else
glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
if (length <= 1)
return;
log = malloc(length);
if (program)
glGetProgramInfoLog(id, length, NULL, log);
else
glGetShaderInfoLog(id, length, NULL, log);
log[length - 1] = '\n';
trace("Info log received from %s #%u:\n", program ? "program" : "shader", id);
p = log;
end = &log[length];
while (p < end)
{
line = p;
if ((p = memchr(line, '\n', end - line)))
++p;
else
p = end;
trace(" %.*s", (int)(p - line), line);
}
free(log);
}
static GLuint compile_compute_shader_program(struct gl_runner *runner)
{
struct vkd3d_shader_code cs_code;
GLuint program_id, cs_id;
const GLchar *source;
ID3D10Blob *cs_blob;
GLint status, size;
bool ret;
reset_combined_samplers(runner);
if (!(cs_blob = compile_hlsl(&runner->r, runner->r.cs_source, "cs")))
return false;
ret = compile_shader(runner, cs_blob, &cs_code);
ID3D10Blob_Release(cs_blob);
if (!ret)
return false;
cs_id = glCreateShader(GL_COMPUTE_SHADER);
if (runner->language == SPIR_V)
{
glShaderBinary(1, &cs_id, GL_SHADER_BINARY_FORMAT_SPIR_V, cs_code.code, cs_code.size);
}
else
{
source = cs_code.code;
size = cs_code.size;
glShaderSource(cs_id, 1, &source, &size);
glCompileShader(cs_id);
}
vkd3d_shader_free_shader_code(&cs_code);
if (runner->language == SPIR_V)
p_glSpecializeShader(cs_id, "main", 0, NULL, NULL);
glGetShaderiv(cs_id, GL_COMPILE_STATUS, &status);
ok(status, "Failed to compile compute shader.\n");
trace_info_log(cs_id, false);
program_id = glCreateProgram();
glAttachShader(program_id, cs_id);
glLinkProgram(program_id);
glGetProgramiv(program_id, GL_LINK_STATUS, &status);
ok(status, "Failed to link program.\n");
trace_info_log(program_id, true);
glDeleteShader(cs_id);
return program_id;
}
static bool gl_runner_dispatch(struct shader_runner *r, unsigned int x, unsigned int y, unsigned int z)
{
struct gl_runner *runner = gl_runner(r);
GLuint program_id, ubo_id = 0;
unsigned int i;
program_id = compile_compute_shader_program(runner);
todo_if(runner->r.is_todo) ok(program_id, "Failed to compile shader program.\n");
if (!program_id)
return false;
glUseProgram(program_id);
if (runner->r.uniform_count)
{
glGenBuffers(1, &ubo_id);
glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo_id);
glBufferData(GL_UNIFORM_BUFFER, runner->r.uniform_count * sizeof(*runner->r.uniforms),
runner->r.uniforms, GL_STATIC_DRAW);
}
for (i = 0; i < runner->r.resource_count; ++i)
{
struct gl_resource *resource = gl_resource(runner->r.resources[i]);
switch (resource->r.desc.type)
{
case RESOURCE_TYPE_RENDER_TARGET:
case RESOURCE_TYPE_DEPTH_STENCIL:
case RESOURCE_TYPE_VERTEX_BUFFER:
case RESOURCE_TYPE_TEXTURE:
break;
case RESOURCE_TYPE_UAV:
if (resource->r.desc.dimension != RESOURCE_DIMENSION_BUFFER)
glBindImageTexture(resource->r.desc.slot, resource->id, 0, GL_TRUE,
0, GL_READ_WRITE, resource->format->internal_format);
break;
}
}
glDispatchCompute(x, y, z);
glDeleteBuffers(1, &ubo_id);
glDeleteProgram(program_id);
return true;
}
static GLenum get_topology_gl(D3D_PRIMITIVE_TOPOLOGY topology)
{
switch (topology)
{
case D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST:
return GL_TRIANGLES;
case D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP:
return GL_TRIANGLE_STRIP;
default:
fatal_error("Unhandled topology %#x.\n", topology);
}
}
static GLenum get_texture_wrap_gl(D3D12_TEXTURE_ADDRESS_MODE mode)
{
switch (mode)
{
case D3D12_TEXTURE_ADDRESS_MODE_WRAP:
return GL_REPEAT;
case D3D12_TEXTURE_ADDRESS_MODE_MIRROR:
return GL_MIRRORED_REPEAT;
case D3D12_TEXTURE_ADDRESS_MODE_CLAMP:
return GL_CLAMP_TO_EDGE;
case D3D12_TEXTURE_ADDRESS_MODE_BORDER:
return GL_CLAMP_TO_BORDER_ARB;
case D3D12_TEXTURE_ADDRESS_MODE_MIRROR_ONCE:
return GL_MIRROR_CLAMP_TO_EDGE;
default:
fatal_error("Unhandled address mode %#x.\n", mode);
}
}
static GLenum get_texture_filter_mag_gl(D3D12_FILTER filter)
{
return filter & 0x4 ? GL_LINEAR : GL_NEAREST;
}
static GLenum get_texture_filter_min_gl(D3D12_FILTER filter)
{
if (filter & 0x1)
return filter & 0x10 ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR_MIPMAP_NEAREST;
else
return filter & 0x10 ? GL_NEAREST_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_NEAREST;
}
static GLenum get_compare_op_gl(D3D12_COMPARISON_FUNC op)
{
switch (op)
{
case D3D12_COMPARISON_FUNC_NEVER:
return GL_NEVER;
case D3D12_COMPARISON_FUNC_LESS:
return GL_LESS;
case D3D12_COMPARISON_FUNC_EQUAL:
return GL_EQUAL;
case D3D12_COMPARISON_FUNC_LESS_EQUAL:
return GL_LEQUAL;
case D3D12_COMPARISON_FUNC_GREATER:
return GL_GREATER;
case D3D12_COMPARISON_FUNC_NOT_EQUAL:
return GL_NOTEQUAL;
case D3D12_COMPARISON_FUNC_GREATER_EQUAL:
return GL_GEQUAL;
case D3D12_COMPARISON_FUNC_ALWAYS:
return GL_ALWAYS;
default:
fatal_error("Unhandled compare op %#x.\n", op);
}
}
static GLuint compile_graphics_shader_program(struct gl_runner *runner, ID3D10Blob **vs_blob)
{
ID3D10Blob *fs_blob, *hs_blob = NULL, *ds_blob = NULL, *gs_blob = NULL;
struct vkd3d_shader_code vs_code, fs_code;
GLuint program_id, vs_id, fs_id;
const GLchar *source;
GLint status, size;
bool succeeded;
reset_combined_samplers(runner);
*vs_blob = compile_hlsl(&runner->r, runner->r.vs_source, "vs");
fs_blob = compile_hlsl(&runner->r, runner->r.ps_source, "ps");
succeeded = *vs_blob && fs_blob;
if (runner->r.hs_source)
{
hs_blob = compile_hlsl(&runner->r, runner->r.hs_source, "hs");
succeeded = succeeded && hs_blob;
}
if (runner->r.ds_source)
{
ds_blob = compile_hlsl(&runner->r, runner->r.ds_source, "ds");
succeeded = succeeded && ds_blob;
}
if (runner->r.gs_source)
{
gs_blob = compile_hlsl(&runner->r, runner->r.gs_source, "gs");
succeeded = succeeded && gs_blob;
}
if (!succeeded)
{
if (*vs_blob)
ID3D10Blob_Release(*vs_blob);
if (fs_blob)
ID3D10Blob_Release(fs_blob);
if (hs_blob)
ID3D10Blob_Release(hs_blob);
if (ds_blob)
ID3D10Blob_Release(ds_blob);
if (gs_blob)
ID3D10Blob_Release(gs_blob);
return false;
}
if (!compile_shader(runner, *vs_blob, &vs_code))
{
ID3D10Blob_Release(fs_blob);
ID3D10Blob_Release(*vs_blob);
return false;
}
if (!compile_shader(runner, fs_blob, &fs_code))
{
vkd3d_shader_free_shader_code(&vs_code);
ID3D10Blob_Release(fs_blob);
ID3D10Blob_Release(*vs_blob);
return false;
}
ID3D10Blob_Release(fs_blob);
/* TODO: compile and use the hs, ds and/or gs blobs too, but currently this
* point is not reached because compile_hlsl() fails on these. */
if (hs_blob)
ID3D10Blob_Release(hs_blob);
if (ds_blob)
ID3D10Blob_Release(ds_blob);
vs_id = glCreateShader(GL_VERTEX_SHADER);
if (runner->language == SPIR_V)
{
glShaderBinary(1, &vs_id, GL_SHADER_BINARY_FORMAT_SPIR_V, vs_code.code, vs_code.size);
}
else
{
source = vs_code.code;
size = vs_code.size;
glShaderSource(vs_id, 1, &source, &size);
glCompileShader(vs_id);
}
vkd3d_shader_free_shader_code(&vs_code);
if (runner->language == SPIR_V)
p_glSpecializeShader(vs_id, "main", 0, NULL, NULL);
glGetShaderiv(vs_id, GL_COMPILE_STATUS, &status);
ok(status, "Failed to compile vertex shader.\n");
trace_info_log(vs_id, false);
fs_id = glCreateShader(GL_FRAGMENT_SHADER);
if (runner->language == SPIR_V)
{
glShaderBinary(1, &fs_id, GL_SHADER_BINARY_FORMAT_SPIR_V, fs_code.code, fs_code.size);
}
else
{
source = fs_code.code;
size = fs_code.size;
glShaderSource(fs_id, 1, &source, &size);
glCompileShader(fs_id);
}
vkd3d_shader_free_shader_code(&fs_code);
if (runner->language == SPIR_V)
p_glSpecializeShader(fs_id, "main", 0, NULL, NULL);
glGetShaderiv(fs_id, GL_COMPILE_STATUS, &status);
ok(status, "Failed to compile fragment shader.\n");
trace_info_log(fs_id, false);
program_id = glCreateProgram();
glAttachShader(program_id, vs_id);
glAttachShader(program_id, fs_id);
glLinkProgram(program_id);
glGetProgramiv(program_id, GL_LINK_STATUS, &status);
ok(status, "Failed to link program.\n");
trace_info_log(program_id, true);
glDeleteShader(fs_id);
glDeleteShader(vs_id);
return program_id;
}
static void gl_runner_clear(struct shader_runner *r, struct resource *res, const struct vec4 *clear_value)
{
struct gl_resource *resource = gl_resource(res);
struct gl_runner *runner = gl_runner(r);
GLbitfield clear_mask;
if (!runner->fbo_id)
glGenFramebuffers(1, &runner->fbo_id);
glBindFramebuffer(GL_FRAMEBUFFER, runner->fbo_id);
switch (resource->r.desc.type)
{
case RESOURCE_TYPE_RENDER_TARGET:
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, resource->id, 0);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glClearColor(clear_value->x, clear_value->y, clear_value->z, clear_value->w);
clear_mask = GL_COLOR_BUFFER_BIT;
break;
case RESOURCE_TYPE_DEPTH_STENCIL:
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, resource->id, 0);
glDepthMask(GL_TRUE);
glClearDepthf(clear_value->x);
clear_mask = GL_DEPTH_BUFFER_BIT;
break;
default:
fatal_error("Clears are not implemented for resource type %u.\n", resource->r.desc.type);
}
glScissor(0, 0, res->desc.width, res->desc.height);
glClear(clear_mask);
}
static bool gl_runner_draw(struct shader_runner *r,
D3D_PRIMITIVE_TOPOLOGY topology, unsigned int vertex_count, unsigned int instance_count)
{
struct vkd3d_shader_signature vs_input_signature;
unsigned int attribute_idx, rt_count, i, j;
struct gl_runner *runner = gl_runner(r);
struct vkd3d_shader_code vs_dxbc;
uint8_t *attribute_offsets[32];
struct
{
GLuint id;
GLsizei stride;
} vbo_info[MAX_RESOURCES];
GLuint program_id, ubo_id = 0;
ID3D10Blob *vs_blob;
uint32_t map;
int ret;
struct
{
GLuint id;
} sampler_info[MAX_SAMPLERS];
GLenum draw_buffers[8];
program_id = compile_graphics_shader_program(runner, &vs_blob);
todo_if(runner->r.is_todo) ok(program_id, "Failed to compile shader program.\n");
if (!program_id)
return false;
glUseProgram(program_id);
if (runner->r.uniform_count)
{
glGenBuffers(1, &ubo_id);
glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo_id);
glBufferData(GL_UNIFORM_BUFFER, runner->r.uniform_count * sizeof(*runner->r.uniforms),
runner->r.uniforms, GL_STATIC_DRAW);
}
if (!runner->fbo_id)
glGenFramebuffers(1, &runner->fbo_id);
glBindFramebuffer(GL_FRAMEBUFFER, runner->fbo_id);
for (i = 0; i < runner->r.sampler_count; ++i)
{
struct sampler *sampler = &runner->r.samplers[i];
GLuint id;
glGenSamplers(1, &id);
glSamplerParameteri(id, GL_TEXTURE_WRAP_S, get_texture_wrap_gl(sampler->u_address));
glSamplerParameteri(id, GL_TEXTURE_WRAP_T, get_texture_wrap_gl(sampler->v_address));
glSamplerParameteri(id, GL_TEXTURE_WRAP_R, get_texture_wrap_gl(sampler->w_address));
glSamplerParameteri(id, GL_TEXTURE_MAG_FILTER, get_texture_filter_mag_gl(sampler->filter));
glSamplerParameteri(id, GL_TEXTURE_MIN_FILTER, get_texture_filter_min_gl(sampler->filter));
if (sampler->func)
{
glSamplerParameteri(id, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
glSamplerParameteri(id, GL_TEXTURE_COMPARE_FUNC, get_compare_op_gl(sampler->func));
}
sampler_info[i].id = id;
}
for (i = 0; i < runner->combined_sampler_count; ++i)
{
const struct vkd3d_shader_combined_resource_sampler *s = &runner->combined_samplers[i];
struct resource *resource;
struct sampler *sampler;
if (s->resource_space || s->sampler_space)
fatal_error("Unsupported register space.\n");
if (!(resource = shader_runner_get_resource(r, RESOURCE_TYPE_TEXTURE, s->resource_index)))
fatal_error("Resource not found.\n");
glActiveTexture(GL_TEXTURE0 + s->binding.binding);
if (resource->desc.dimension == RESOURCE_DIMENSION_BUFFER)
glBindTexture(GL_TEXTURE_BUFFER, gl_resource(resource)->tbo_id);
else if (resource->desc.sample_count > 1)
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, gl_resource(resource)->id);
else
glBindTexture(GL_TEXTURE_2D, gl_resource(resource)->id);
if (s->sampler_index == VKD3D_SHADER_DUMMY_SAMPLER_INDEX)
continue;
if (!(sampler = shader_runner_get_sampler(r, s->sampler_index)))
fatal_error("Sampler not found.\n");
glBindSampler(s->binding.binding, sampler_info[sampler - r->samplers].id);
}
memset(vbo_info, 0, sizeof(vbo_info));
memset(draw_buffers, 0, sizeof(draw_buffers));
for (i = 0, rt_count = 0; i < runner->r.resource_count; ++i)
{
struct gl_resource *resource = gl_resource(runner->r.resources[i]);
switch (resource->r.desc.type)
{
case RESOURCE_TYPE_RENDER_TARGET:
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + resource->r.desc.slot, resource->id, 0);
if (resource->r.desc.slot >= ARRAY_SIZE(draw_buffers))
fatal_error("Unsupported render target index %u.\n", resource->r.desc.slot);
draw_buffers[resource->r.desc.slot] = GL_COLOR_ATTACHMENT0 + resource->r.desc.slot;
if (resource->r.desc.slot >= rt_count)
rt_count = resource->r.desc.slot + 1;
break;
case RESOURCE_TYPE_DEPTH_STENCIL:
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, resource->id, 0);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glDepthFunc(get_compare_op_gl(runner->r.depth_func));
break;
case RESOURCE_TYPE_TEXTURE:
break;
case RESOURCE_TYPE_UAV:
if (resource->r.desc.dimension == RESOURCE_DIMENSION_BUFFER)
{
glBindImageTexture(resource->r.desc.slot, resource->tbo_id, 0, GL_TRUE,
0, GL_READ_WRITE, resource->format->internal_format);
}
else
{
glBindImageTexture(resource->r.desc.slot, resource->id, 0, GL_TRUE,
0, GL_READ_WRITE, resource->format->internal_format);
}
break;
case RESOURCE_TYPE_VERTEX_BUFFER:
assert(resource->r.desc.slot < ARRAY_SIZE(vbo_info));
vbo_info[resource->r.desc.slot].id = resource->id;
for (j = 0; j < runner->r.input_element_count; ++j)
{
if (runner->r.input_elements[j].slot != resource->r.desc.slot)
continue;
assert(j < ARRAY_SIZE(attribute_offsets));
attribute_offsets[j] = (uint8_t *)(uintptr_t)vbo_info[resource->r.desc.slot].stride;
vbo_info[resource->r.desc.slot].stride += runner->r.input_elements[j].texel_size;
}
break;
}
}
glEnable(GL_SAMPLE_MASK);
glSampleMaski(0, runner->r.sample_mask);
glViewport(0, 0, RENDER_TARGET_WIDTH, RENDER_TARGET_HEIGHT);
glScissor(0, 0, RENDER_TARGET_WIDTH, RENDER_TARGET_HEIGHT);
glDrawBuffers(rt_count, draw_buffers);
vs_dxbc.code = ID3D10Blob_GetBufferPointer(vs_blob);
vs_dxbc.size = ID3D10Blob_GetBufferSize(vs_blob);
ret = vkd3d_shader_parse_input_signature(&vs_dxbc, &vs_input_signature, NULL);
ok(!ret, "Failed to parse input signature, error %d.\n", ret);
map = runner->attribute_map;
for (i = 0, runner->attribute_map = 0; i < runner->r.input_element_count; ++i)
{
const struct input_element *element = &runner->r.input_elements[i];
const struct vkd3d_shader_signature_element *signature_element;
const struct format_info *format;
signature_element = vkd3d_shader_find_signature_element(&vs_input_signature,
element->name, element->index, 0);
ok(signature_element, "Cannot find signature element %s%u.\n", element->name, element->index);
attribute_idx = signature_element->register_index;
format = get_format_info(element->format, false);
glBindBuffer(GL_ARRAY_BUFFER, vbo_info[element->slot].id);
if (format->is_integer)
glVertexAttribIPointer(attribute_idx, format->component_count, format->type,
vbo_info[element->slot].stride, attribute_offsets[i]);
else
glVertexAttribPointer(attribute_idx, format->component_count, format->type,
GL_FALSE, vbo_info[element->slot].stride, attribute_offsets[i]);
glEnableVertexAttribArray(attribute_idx);
runner->attribute_map |= attribute_idx;
}
vkd3d_shader_free_shader_signature(&vs_input_signature);
map &= ~runner->attribute_map;
for (attribute_idx = 0; map; ++attribute_idx, map >>= 1)
{
if (map & 1)
glDisableVertexAttribArray(attribute_idx);
}
glDrawArraysInstanced(get_topology_gl(topology), 0, vertex_count, instance_count);
for (i = 0; i < runner->r.sampler_count; ++i)
{
glDeleteSamplers(1, &sampler_info[i].id);
}
glDeleteBuffers(1, &ubo_id);
ID3D10Blob_Release(vs_blob);
glDeleteProgram(program_id);
return true;
}
struct gl_resource_readback
{
struct resource_readback rb;
};
static struct resource_readback *gl_runner_get_resource_readback(struct shader_runner *r, struct resource *res)
{
struct gl_resource *resource = gl_resource(res);
struct gl_runner *runner = gl_runner(r);
struct resource_readback *rb;
if (resource->r.desc.type != RESOURCE_TYPE_RENDER_TARGET && resource->r.desc.type != RESOURCE_TYPE_DEPTH_STENCIL
&& resource->r.desc.type != RESOURCE_TYPE_UAV)
fatal_error("Unhandled resource type %#x.\n", resource->r.desc.type);
rb = malloc(sizeof(*rb));
rb->width = resource->r.desc.width;
rb->height = resource->r.desc.height;
rb->depth = 1;
rb->row_pitch = rb->width * resource->r.desc.texel_size;
rb->data = malloc(rb->row_pitch * rb->height);
if (resource->r.desc.dimension == RESOURCE_DIMENSION_BUFFER)
{
glBindBuffer(GL_TEXTURE_BUFFER, resource->id);
glGetBufferSubData(GL_TEXTURE_BUFFER, 0, rb->row_pitch * rb->height, rb->data);
}
else if (resource->r.desc.sample_count > 1)
{
GLuint src_fbo, dst_fbo;
GLuint resolved;
glGenTextures(1, &resolved);
glBindTexture(GL_TEXTURE_2D, resolved);
glTexStorage2D(GL_TEXTURE_2D, resource->r.desc.level_count,
resource->format->internal_format, resource->r.desc.width, resource->r.desc.height);
glGenFramebuffers(1, &src_fbo);
glGenFramebuffers(1, &dst_fbo);
glBindFramebuffer(GL_READ_FRAMEBUFFER, src_fbo);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst_fbo);
glFramebufferTexture(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, resource->id, 0);
glFramebufferTexture(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, resolved, 0);
glBlitFramebuffer(0, 0, resource->r.desc.width, resource->r.desc.height,
0, 0, resource->r.desc.width, resource->r.desc.height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer(GL_FRAMEBUFFER, runner->fbo_id);
glDeleteFramebuffers(1, &src_fbo);
glDeleteFramebuffers(1, &dst_fbo);
glGetTexImage(GL_TEXTURE_2D, 0, resource->format->format, resource->format->type, rb->data);
glDeleteTextures(1, &resolved);
}
else
{
glBindTexture(GL_TEXTURE_2D, resource->id);
glGetTexImage(GL_TEXTURE_2D, 0, resource->format->format, resource->format->type, rb->data);
}
return rb;
}
static void gl_runner_release_readback(struct shader_runner *runner, struct resource_readback *rb)
{
free(rb->data);
free(rb);
}
static const struct shader_runner_ops gl_runner_ops =
{
.create_resource = gl_runner_create_resource,
.destroy_resource = gl_runner_destroy_resource,
.dispatch = gl_runner_dispatch,
.clear = gl_runner_clear,
.draw = gl_runner_draw,
.get_resource_readback = gl_runner_get_resource_readback,
.release_readback = gl_runner_release_readback,
};
static void run_tests(enum shading_language language)
{
struct gl_runner runner;
if (!gl_runner_init(&runner, language))
return;
run_shader_tests(&runner.r, &runner.caps, &gl_runner_ops, NULL);
gl_runner_cleanup(&runner);
}
void run_shader_tests_gl(void)
{
const char *test_name;
test_name = vkd3d_test_name;
vkd3d_test_name = "shader_runner_gl";
run_tests(SPIR_V);
run_tests(GLSL);
vkd3d_test_name = test_name;
}
#endif