/* * 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 #include #include #include "shader_runner.h" #include "vkd3d_d3dcompiler.h" static PFNGLDEPTHBOUNDSEXTPROC p_glDepthBoundsEXT; 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.shader_caps[SHADER_CAP_FLOAT64] = true; if (check_gl_extension("GL_ARB_gpu_shader_int64", count)) runner->caps.shader_caps[SHADER_CAP_INT64] = true; if (check_gl_extension("GL_EXT_depth_bounds_test", count)) runner->caps.shader_caps[SHADER_CAP_DEPTH_BOUNDS] = 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 bool check_glsl_support(void) { const enum vkd3d_shader_target_type *target_types; unsigned int count, i; target_types = vkd3d_shader_get_supported_target_types(VKD3D_SHADER_SOURCE_DXBC_TPF, &count); for (i = 0; i < count; ++i) { if (target_types[i] == VKD3D_SHADER_TARGET_GLSL) return true; } 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, }; if (language == GLSL && !check_glsl_support()) { skip("GLSL support is not enabled. If this is unintentional, " "add -DVKD3D_SHADER_UNSUPPORTED_GLSL to CPPFLAGS.\n"); return false; } 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; runner->caps.shader_caps[SHADER_CAP_GEOMETRY_SHADER] = true; 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_glDepthBoundsEXT = (void *)eglGetProcAddress("glDepthBoundsEXT"); 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)); if (runner->r.depth_bounds) { glEnable(GL_DEPTH_BOUNDS_TEST_EXT); p_glDepthBoundsEXT(runner->r.depth_min, runner->r.depth_max); } 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