diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | CMakeLists.txt | 17 | ||||
-rw-r--r-- | src/waffle/CMakeLists.txt | 47 | ||||
-rw-r--r-- | src/waffle/dispatch/dispatch_common.c | 179 | ||||
-rw-r--r-- | src/waffle/dispatch/dispatch_common.h | 31 | ||||
-rwxr-xr-x | src/waffle/dispatch/gen_dispatch.py | 357 |
6 files changed, 635 insertions, 0 deletions
@@ -51,6 +51,10 @@ Makefile /examples/simple-x11-egl /lib/ /src/waffle/libwaffle_static.a +/src/waffle/dispatch/gl_dispatch.c +/src/waffle/dispatch/gl_dispatch.h +/src/waffle/dispatch/glx_dispatch.c +/src/waffle/dispatch/glx_dispatch.h /tests/functional/gl_basic_test /tests/waffle_test/libwaffle_test.dylib /tests/unittests/waffle-unittest diff --git a/CMakeLists.txt b/CMakeLists.txt index a6695fe..48ff5ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,23 @@ include(GNUInstallDirs) find_package(PkgConfig) +# Check for presence of Python 2.6 or greater. +foreach(python_cmd python2 python) + execute_process( + COMMAND ${python_cmd} -c "import sys; assert '2.6' <= sys.version < '3'" + OUTPUT_QUIET + ERROR_QUIET + RESULT_VARIABLE python_version_check_error_code) + if(python_version_check_error_code EQUAL 0) + set(python ${python_cmd}) + break() + endif(python_version_check_error_code EQUAL 0) +endforeach(python_cmd) + +if(NOT DEFINED python) + message(FATAL_ERROR "python version 2.x (where x >= 6) required") +endif(NOT DEFINED python) + # ------------------------------------------------------------------------------ # Add subdirectories # ------------------------------------------------------------------------------ diff --git a/src/waffle/CMakeLists.txt b/src/waffle/CMakeLists.txt index a743096..986884d 100644 --- a/src/waffle/CMakeLists.txt +++ b/src/waffle/CMakeLists.txt @@ -39,6 +39,15 @@ set(waffle_libdeps ${xcb_LDFLAGS} ) + +set(gl_generated_dispatch_c + ${CMAKE_CURRENT_BINARY_DIR}/dispatch/gl_dispatch.c + ) + +set(glx_generated_dispatch_c + ${CMAKE_CURRENT_BINARY_DIR}/dispatch/glx_dispatch.c + ) + set(waffle_sources api/api_priv.c api/waffle_attrib_list.c @@ -57,6 +66,9 @@ set(waffle_sources core/wcore_error.c core/wcore_tinfo.c core/wcore_util.c + dispatch/dispatch_common.c + ${gl_generated_dispatch_c} + ${glx_generated_dispatch_c} ) if(waffle_on_mac) @@ -193,3 +205,38 @@ set_target_properties(waffle_static PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib ) + +set(gl_generated_dispatch_h ${CMAKE_CURRENT_BINARY_DIR}/dispatch/gl_dispatch.h) +set(glx_generated_dispatch_h ${CMAKE_CURRENT_BINARY_DIR}/dispatch/glx_dispatch.h) + +add_custom_command( + OUTPUT ${gl_generated_dispatch_c} + ${gl_generated_dispatch_h} + COMMAND ${python} + dispatch/gen_dispatch.py + --output-c ${gl_generated_dispatch_c} + --output-h ${gl_generated_dispatch_h} + dispatch/gl.xml + DEPENDS dispatch/gen_dispatch.py dispatch/gl.xml + ) + +add_custom_command( + OUTPUT ${glx_generated_dispatch_c} + ${glx_generated_dispatch_h} + COMMAND ${python} + dispatch/gen_dispatch.py + --output-c ${glx_generated_dispatch_c} + --output-h ${glx_generated_dispatch_h} + dispatch/glx.xml + DEPENDS dispatch/gen_dispatch.py dispatch/glx.xml + ) + +add_custom_target(gen_dispatch + DEPENDS ${dispatch_gl_outputs} + ) + +install( + FILES ${gl_generated_dispatch_h} + ${glx_generated_dispatch_h} + DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR}/${waffle_libname} + ) diff --git a/src/waffle/dispatch/dispatch_common.c b/src/waffle/dispatch/dispatch_common.c new file mode 100644 index 0000000..1af1a44 --- /dev/null +++ b/src/waffle/dispatch/dispatch_common.c @@ -0,0 +1,179 @@ +/* + * Copyright © 2013 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. + */ + +/** + * @file dispatch_common.c + * + * Implements common code shared by the generated GL/EGL/GLX dispatch code. + * + * A collection of some important specs on getting GL function pointers. + * + * From the linux GL ABI (http://www.opengl.org/registry/ABI/): + * + * "3.4. The libraries must export all OpenGL 1.2, GLU 1.3, GLX 1.3, and + * ARB_multitexture entry points statically. + * + * 3.5. Because non-ARB extensions vary so widely and are constantly + * increasing in number, it's infeasible to require that they all be + * supported, and extensions can always be added to hardware drivers + * after the base link libraries are released. These drivers are + * dynamically loaded by libGL, so extensions not in the base + * library must also be obtained dynamically. + * + * 3.6. To perform the dynamic query, libGL also must export an entry + * point called + * + * void (*glXGetProcAddressARB(const GLubyte *))(); + * + * The full specification of this function is available separately. It + * takes the string name of a GL or GLX entry point and returns a pointer + * to a function implementing that entry point. It is functionally + * identical to the wglGetProcAddress query defined by the Windows OpenGL + * library, except that the function pointers returned are context + * independent, unlike the WGL query." + * + * From the EGL 1.4 spec: + * + * "Client API function pointers returned by eglGetProcAddress are + * independent of the display and the currently bound client API context, + * and may be used by any client API context which supports the extension. + * + * eglGetProcAddress may be queried for all of the following functions: + * + * • All EGL and client API extension functions supported by the + * implementation (whether those extensions are supported by the current + * client API context or not). This includes any mandatory OpenGL ES + * extensions. + * + * eglGetProcAddress may not be queried for core (non-extension) functions + * in EGL or client APIs 20 . + * + * For functions that are queryable with eglGetProcAddress, + * implementations may choose to also export those functions statically + * from the object libraries im- plementing those functions. However, + * portable clients cannot rely on this behavior. + * + * From the GLX 1.4 spec: + * + * "glXGetProcAddress may be queried for all of the following functions: + * + * • All GL and GLX extension functions supported by the implementation + * (whether those extensions are supported by the current context or + * not). + * + * • All core (non-extension) functions in GL and GLX from version 1.0 up + * to and including the versions of those specifications supported by + * the implementation, as determined by glGetString(GL VERSION) and + * glXQueryVersion queries." + */ + +#include <string.h> +#include <stdio.h> +#include "dispatch_common.h" +#include "api_priv.h" +#include "gl_dispatch.h" +#include "glx_dispatch.h" + +bool +is_desktop_gl(void) +{ + return true; /* XXX */ +} + +bool +has_gl_version(int ver) +{ + GLint major, minor; + + glGetIntegerv(GL_MAJOR_VERSION, &major); + glGetIntegerv(GL_MAJOR_VERSION, &minor); + + return major * 10 + minor >= ver; +} + +static bool +extension_in_string(const char *extension_list, const char *ext) +{ + const char *ptr = extension_list; + int len = strlen(ext); + + /* Make sure that don't just find an extension with our name as a prefix. */ + do { + ptr = strstr(ptr, ext); + } while (ptr && (ptr[len] != ' ' && ptr[len] != 0)); + + return ptr != NULL; +} + +bool +has_gl_extension(const char *ext) +{ + return extension_in_string((const char *)glGetString(GL_EXTENSIONS), ext); +} + +bool +has_egl_extension(const char *ext) +{ +/* + return extension_in_string(eglQueryString(EGL_EXTENSIONS), ext); +*/ + return true; /* XXX */ +} + +bool +has_glx_extension(const char *ext) +{ + /* No, you can't just use glXGetClientString or glXGetServerString() here. + * Those each tell you about one half of what's needed for an extension to + * be supported, and glXQueryExtensionsString(). + */ + Display *dpy = glXGetCurrentDisplay(); + int screen = 0; + + if (!dpy) { + fprintf(stderr, "waffle needs a display!"); /* XXX */ + return false; + } + + return extension_in_string(glXQueryExtensionsString(dpy, screen), ext); +} + +void +waffle_dispatch_platform_autoinit(void) +{ + static const int32_t attribs[] = {WAFFLE_PLATFORM, WAFFLE_PLATFORM_GLX, 0}; + + if (api_platform) + return; + + waffle_init(attribs); + +#if 0 + static struct wcore_platform *glx_plat = NULL; + + + if (!glx_plat) + glx_plat = glx_platform_create(); +#endif +} + diff --git a/src/waffle/dispatch/dispatch_common.h b/src/waffle/dispatch/dispatch_common.h new file mode 100644 index 0000000..736325c --- /dev/null +++ b/src/waffle/dispatch/dispatch_common.h @@ -0,0 +1,31 @@ +/* + * Copyright © 2013 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 <stdbool.h> + +bool is_desktop_gl(void); +bool has_gl_version(int ver); +bool has_gl_extension(const char *ext); +bool has_glx_extension(const char *ext); +bool has_egl_extension(const char *ext); +void waffle_dispatch_platform_autoinit(void); diff --git a/src/waffle/dispatch/gen_dispatch.py b/src/waffle/dispatch/gen_dispatch.py new file mode 100755 index 0000000..5b4336c --- /dev/null +++ b/src/waffle/dispatch/gen_dispatch.py @@ -0,0 +1,357 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright © 2013 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. + +import sys +import argparse +import xml.etree.ElementTree as ET +import re + +class GLFunction(object): + def __init__(self, ret_type, name): + self.name = name + self.ptr_type = 'PFN' + name.upper() + self.ret_type = ret_type + self.providers = [] + self.args = [] + self.args_list = '' + self.args_decl = 'void' + + def add_arg(self, type, name): + self.args.append((type, name)) + if self.args_decl == 'void': + self.args_list = name + self.args_decl = type + ' ' + name + else: + self.args_list += ', ' + name + self.args_decl += ', ' + type + ' ' + name + + def add_provider(self, condition, loader, human_name): + self.providers.append((condition, loader, human_name)) + +class Generator(object): + def __init__(self): + self.enums = {} + self.functions = {} + self.max_enum_name_len = 1 + self.copyright_comment = None + self.typedefs = '' + self.out_file = None + + def all_text_until_element_name(self, element, element_name): + text = '' + + if element.text is not None: + text += element.text + + for child in element: + if child.tag == element_name: + break + if child.text: + text += child.text + if child.tail: + text += child.tail + return text + + def out(self, text): + self.out_file.write(text) + + def outln(self, text): + self.out_file.write(text + '\n') + + def parse_typedefs(self, reg): + for t in reg.findall('types/type'): + if 'name' in t.attrib and t.attrib['name'] not in {'GLhandleARB'}: + continue + + if t.text is not None: + self.typedefs += t.text + + for child in t: + if child.text: + self.typedefs += child.text + if child.tail: + self.typedefs += child.tail + self.typedefs += '\n' + + def parse_enums(self, reg): + for enum in reg.findall('enums/enum'): + name = enum.get('name') + self.max_enum_name_len = max(self.max_enum_name_len, len(name)) + self.enums[name] = enum.get('value') + + def get_function_return_type(self, proto): + # Everything up to the start of the name element is the return type. + return self.all_text_until_element_name(proto, 'name').strip() + + def parse_function_definitions(self, reg): + for command in reg.findall('commands/command'): + proto = command.find('proto') + name = proto.find('name').text + ret_type = self.get_function_return_type(proto) + + func = GLFunction(ret_type, name) + + for arg in command.findall('param'): + func.add_arg(self.all_text_until_element_name(arg, 'name').strip(), + arg.find('name').text) + + self.functions[name] = func + + def drop_weird_glx_functions(self): + # Drop a few ancient SGIX GLX extensions that use types not defined + # anywhere in Xlib. In glxext.h, they're protected by #ifdefs for the + # headers that defined them. + weird_functions = [name for name, func in self.functions.iteritems() + if 'VLServer' in func.args_decl + or 'DMparams' in func.args_decl] + + for name in weird_functions: + del self.functions[name] + + def process_require_statements(self, feature, condition, loader, human_name): + for command in feature.findall('require/command'): + name = command.get('name') + func = self.functions[name] + func.add_provider(condition, loader.format(name), human_name) + + def parse_function_providers(self, reg): + for feature in reg.findall('feature'): + api = feature.get('api') # string gl, gles1, gles2, glx + if api == 'gl': + m = re.match('GL_VERSION_([0-9])_([0-9])', feature.get('name')) + human_name = 'Desktop OpenGL {0}.{1}'.format(m.group(1), m.group(2)) + gl_ver = int(m.group(1)) * 10 + int(m.group(2)) + condition = 'is_desktop_gl()' + + # Everything in GL 1.2 is guaranteed to be present as + # public symbols in the Linux libGL ABI. Everything + # else is supposed to not be present, so you have to + # glXGetProcAddress() it. + if gl_ver <= 12: + loader = 'waffle_dl_sym(WAFFLE_DL_OPENGL, "{0}")' + else: + loader = 'waffle_get_proc_address("{0}")' + condition += ' && has_gl_version({0})'.format(gl_ver) + else: + human_name = '' + condition = 'true' + loader = 'waffle_dl_sym(WAFFLE_DL_OPENGL, "{0}")' + + self.process_require_statements(feature, condition, loader, human_name) + + for extension in reg.findall('extensions/extension'): + extname = extension.get('name') + # 'supported' is a set of strings like gl, gles1, gles2, or glx, which are + # separated by '|' + apis = extension.get('supported').split('|') + if 'glx' in apis: + human_name = 'GLX extension \\"{0}\\"'.format(extname) + condition = 'has_glx_extension("{0}")'.format(extname) + loader = 'waffle_get_proc_address("{0}")' + self.process_require_statements(extension, condition, loader, human_name) + if 'gl' in apis: + human_name = 'GL extension \\"{0}\\"'.format(extname) + condition = 'has_gl_extension("{0}")'.format(extname) + loader = 'waffle_get_proc_address("{0}")' + self.process_require_statements(extension, condition, loader, human_name) + + def parse(self, file): + reg = ET.parse(file) + if reg.find('comment') != None: + self.copyright_comment = reg.find('comment').text + else: + self.copyright_comment = '' + self.parse_typedefs(reg) + self.parse_enums(reg) + self.parse_function_definitions(reg) + self.parse_function_providers(reg) + + def write_copyright_comment_body(self): + for line in self.copyright_comment.splitlines(): + if '-----' in line: + break + self.outln(' * ' + line) + + def write_enums(self): + for name, value in self.enums.iteritems(): + self.outln('#define ' + name.ljust(self.max_enum_name_len + 3) + value + '') + + def write_function_ptr_typedefs(self): + for func in self.functions.values(): + self.outln('typedef {0} (*{1})({2});'.format(func.ret_type, func.ptr_type, + func.args_decl)) + + def write_dispatch_defines(self): + for func in self.functions.values(): + self.outln('#define {0} waffle_dispatch_{0}'.format(func.name)) + + def write_header(self, file): + self.out_file = open(file, 'w') + + self.outln('/* GL dispatch header for waffle users.') + self.outln(' * This is code-generated from the GL API XML files from Khronos.') + self.write_copyright_comment_body() + self.outln(' */') + self.outln('') + + self.outln('#pragma once') + + self.outln('#include <inttypes.h>') + self.outln('#include <stddef.h>') + self.outln('') + + if 'gl_dispatch.h' not in file: + self.outln('#include "gl_dispatch.h"') + else: + # Add some ridiculous inttypes.h redefinitions that are from + # khrplatform.h and not included in the XML. + self.outln('typedef int8_t khronos_int8_t;') + self.outln('typedef int16_t khronos_int16_t;') + self.outln('typedef int32_t khronos_int32_t;') + self.outln('typedef int64_t khronos_int64_t;') + self.outln('typedef uint8_t khronos_uint8_t;') + self.outln('typedef uint16_t khronos_uint16_t;') + self.outln('typedef uint32_t khronos_uint32_t;') + self.outln('typedef uint64_t khronos_uint64_t;') + self.outln('typedef float khronos_float_t;') + self.outln('typedef intptr_t khronos_intptr_t;') + self.outln('typedef ptrdiff_t khronos_ssize_t;') + + if 'glx_dispatch.h' in file: + self.outln('#include <X11/Xlib.h>') + self.outln('#include <X11/Xutil.h>') + + self.out(self.typedefs) + self.outln('') + self.write_enums() + self.outln('') + self.write_dispatch_defines() + self.outln('') + self.write_function_ptr_typedefs() + + for func in self.functions.values(): + self.outln('{0} waffle_dispatch_{1}({2});'.format(func.ret_type, func.name, + func.args_decl)) + + def write_function_ptr_resolver(self, func): + self.outln('static {0}'.format(func.ptr_type)) + self.outln('waffle_dispatch_{0}_resolver(void)'.format(func.name)) + self.outln('{') + + self.outln(' waffle_dispatch_platform_autoinit();') + self.outln('') + # Walk the sources of aliases of this function and see if we + # have any. If so, get the function pointer and return. + for provider in func.providers: + condition = provider[0] + loader = provider[1] + self.outln(' if ({0})'.format(condition)) + self.outln(' return {0};'.format(loader)) + self.outln('') + + # If the function isn't provided by any known extension, print + # something useful for the poor application developer before + # aborting. (In non-waffle-dispatch GL usage, the app + # developer would call into some blank stub function and + # segfault). + self.outln(' printf("No provider of \\"{0}\\" found. Requires one of:\\n");'.format(func.name)) + if len(func.providers) == 0: + self.outln(' printf(" unknown\\n");') + else: + for provider in func.providers: + self.outln(' printf(" {0}\\n");'.format(provider[2])) + + self.outln(' abort();') + + self.outln('}') + self.outln('') + + def write_dispatch_table_stub(self, func): + dispatch_table_entry = 'dispatch_table->p{0}'.format(func.name) + + self.outln('WAFFLE_API {0}'.format(func.ret_type)) + self.outln('waffle_dispatch_{0}({1})'.format(func.name, func.args_decl)) + self.outln('{') + self.outln(' if (!{0})'.format(dispatch_table_entry)) + self.outln(' {0} = waffle_dispatch_{1}_resolver();'.format(dispatch_table_entry, func.name)) + self.outln('') + if func.ret_type == 'void': + self.outln(' {0}({1});'.format(dispatch_table_entry, func.args_list)) + else: + self.outln(' return {0}({1});'.format(dispatch_table_entry, func.args_list)) + self.outln('}') + self.outln('') + + def write_ifunc_stub(self, func): + self.outln('WAFFLE_API void *waffle_ifunc_dispatch_{0}() __attribute__((ifunc("waffle_dispatch_{0}_resolver")));'.format(func.name)) + self.outln('') + + + def write_source(self, file): + self.out_file = open(file, 'w') + + self.outln('/* GL dispatch code for waffle.') + self.outln(' * This is code-generated from the GL API XML files from Khronos.') + self.write_copyright_comment_body() + self.outln(' */') + self.outln('') + self.outln('#include <stdlib.h>') + self.outln('#include <stdio.h>') + self.outln('') + self.outln('#include "waffle.h"') + self.outln('#include "dispatch_common.h"') + if 'glx_dispatch.c' in file: + self.outln('#include "glx_dispatch.h"') + else: + self.outln('#include "gl_dispatch.h"') + self.outln('') + + self.outln('struct dispatch_table {') + for func in self.functions.values(): + self.outln(' {0} p{1};'.format(func.ptr_type, func.name)) + self.outln('};') + self.outln('') + + self.outln('/* XXX: Make this thread-local and swapped on makecurrent. */') + self.outln('static struct dispatch_table local_dispatch_table;') + self.outln('static struct dispatch_table *dispatch_table = &local_dispatch_table;') + self.outln('') + + for func in self.functions.values(): + self.write_function_ptr_resolver(func) + self.write_dispatch_table_stub(func) + self.write_ifunc_stub(func) + +argparser = argparse.ArgumentParser(description='Generate GL dispatch wrappers.') +argparser.add_argument('--output-c', help='private .c file to be built into libwaffle') +argparser.add_argument('--output-h', help='public .h file to be installed') +argparser.add_argument('files', metavar='file.xml', nargs='+', help='GL API XML files to be parsed') +args = argparser.parse_args() + +generator = Generator() +for file in args.files: + generator.parse(file) +generator.drop_weird_glx_functions() +generator.write_header(args.output_h) +generator.write_source(args.output_c) |