From 5eb1afdcb363c6dd5d47c396bee031bdfc372a33 Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Tue, 5 Jan 2021 23:42:30 +0900 Subject: [PATCH] [vulkan] Implement pipeline creation The prototypes for handle parsers needed to be changes because it turned out "single" was inappropriate for handles as "single" allocates memory for the parsed object, but handles must be written directly. --- include/vid_vulkan.h | 1 + libs/video/renderer/vulkan/qfpipeline.plist | 152 +++++++------- .../renderer/vulkan/vkgen/vkfieldcustom.r | 3 +- .../video/renderer/vulkan/vkgen/vkfieldtype.h | 2 + .../video/renderer/vulkan/vkgen/vkfieldtype.r | 33 +-- libs/video/renderer/vulkan/vkgen/vkhandle.r | 15 +- libs/video/renderer/vulkan/vkparse.c | 188 ++++++++++++------ libs/video/renderer/vulkan/vkparse.h | 14 +- libs/video/renderer/vulkan/vkparse.plist | 19 +- .../video/renderer/vulkan/vulkan_vid_common.c | 9 + 10 files changed, 256 insertions(+), 180 deletions(-) diff --git a/include/vid_vulkan.h b/include/vid_vulkan.h index 74a1ef51a..d34e067fe 100644 --- a/include/vid_vulkan.h +++ b/include/vid_vulkan.h @@ -53,6 +53,7 @@ typedef struct vulkan_ctx_s { VkCommandBuffer cmdbuffer; VkFence fence; // for ctx->cmdbuffer only vulkan_renderpass_t renderpass; + VkPipeline pipeline; size_t curFrame; vulkan_framebufferset_t framebuffers; diff --git a/libs/video/renderer/vulkan/qfpipeline.plist b/libs/video/renderer/vulkan/qfpipeline.plist index 198604466..1f9c6de46 100644 --- a/libs/video/renderer/vulkan/qfpipeline.plist +++ b/libs/video/renderer/vulkan/qfpipeline.plist @@ -39,80 +39,88 @@ ); }; }; - pipelines = { - something = { - stages = ( - { stage = vertex; name = main; module = passthrough; }, - { stage = fragment; name = main; module = pushcolor; }, + pipeline = { + stages = ( + { stage = vertex; name = main; module = passthrough; }, + { stage = fragment; name = main; module = pushcolor; }, + ); + vertexInput = { + bindings = ( + { + binding = 0; + stride = "4 * 4"; + inputRate = vertex; + }, + ); + attributes = ( + { + location = 0; + binding = 0; + format = r32g32b32a32_sfloat; + offset = 0; + }, ); - vertexInput = { - bindings = ( - { - binding = 0; - stride = "4 * 4"; - inputRate = vertex; - }, - ); - attributes = ( - { - location = 0; - binding = 0; - format = r32g32b32a32_sfloat; - offset = 0; - }, - ); - }; - inputAssembly = { - topology = triangle_list; - primitiveRestartEnable = 0; - }; - viewport = { - viewports = ( - { - x = 0; y = 0; - width = 640; height = 480; - minDepth = 0; maxDepth = 1000; - } - ); - scissors = ( - { - offset = { x = 0; y = 0 }; - extent = { width = 640; height = 480; }; - }, - ); - }; - rasterization = { - depthClampEnable = 1; - rasterizerDiscardEnable = 0; - polygonMode = fill; - cullMode = back; - frontFace = counter_clockwise; - depthBiasEnable = 0; - lineWidth = 1; - }; - multisample = { - rasterizationSamples = $msaaSamples; - sampleShadingEnable = 0; - minSampleShading = 0.5; - alphaToCoverageEnable = 0; - alphaToOneEnable = 0; - }; - depthStencil = { - depthTestEnable = 1; - depthWriteEnable = 1; - depthCompareOp = less; - depthBoundsTestEnable = 0; - stencilTestEnable = 0; - }; - colorBlend = { - logicOpEnable = 0; - }; - dymamic = { - dymamicState = ( viewport, scissor ); - }; - layout = something; - renderPass = renderpass; }; + inputAssembly = { + topology = triangle_list; + primitiveRestartEnable = 0; + }; + viewport = { + viewports = ( + { + x = 0; y = 0; + width = 640; height = 480; + minDepth = 0; maxDepth = 1; + } + ); + scissors = ( + { + offset = { x = 0; y = 0 }; + extent = { width = 640; height = 480; }; + }, + ); + }; + rasterization = { + depthClampEnable = 0; + rasterizerDiscardEnable = 0; + polygonMode = fill; + cullMode = back; + frontFace = counter_clockwise; + depthBiasEnable = 0; + lineWidth = 1; + }; + multisample = { + rasterizationSamples = $msaaSamples; + sampleShadingEnable = 0; + minSampleShading = 0.5f; + alphaToCoverageEnable = 0; + alphaToOneEnable = 0; + }; + depthStencil = { + depthTestEnable = 1; + depthWriteEnable = 1; + depthCompareOp = less; + depthBoundsTestEnable = 0; + stencilTestEnable = 0; + }; + colorBlend = { + logicOpEnable = 0; + attachments = ({ + blendEnable = 0; + srcColorBlendFactor = src_color; + dstColorBlendFactor = zero; + colorBlendOp = add; + srcAlphaBlendFactor = src_alpha; + dstAlphaBlendFactor = zero; + alphaBlendOp = add; + colorWriteMask = r|g|b|a; + }); + }; + dynamic = { + dynamicState = ( viewport, scissor ); + }; + layout = something; + //renderPass = renderpass; }; renderpass = { attachments = ( diff --git a/libs/video/renderer/vulkan/vkgen/vkfieldcustom.r b/libs/video/renderer/vulkan/vkgen/vkfieldcustom.r index 7c7843414..611cd7a9b 100644 --- a/libs/video/renderer/vulkan/vkgen/vkfieldcustom.r +++ b/libs/video/renderer/vulkan/vkgen/vkfieldcustom.r @@ -2,6 +2,7 @@ #include #include "vkfieldcustom.h" +#include "vkfieldtype.h" #include "vkgen.h" #include "vktype.h" @@ -15,7 +16,7 @@ } PLItem *desc = [item getObjectForKey:"type"]; - pltype = str_hold ([[desc getObjectAtIndex:1] string]); + pltype = str_hold (parseItemType ([desc getObjectAtIndex:1])); parser = str_hold ([[desc getObjectAtIndex:2] string]); fields = [item getObjectForKey:"fields"]; diff --git a/libs/video/renderer/vulkan/vkgen/vkfieldtype.h b/libs/video/renderer/vulkan/vkgen/vkfieldtype.h index fa27c7d8e..8e5d26bf8 100644 --- a/libs/video/renderer/vulkan/vkgen/vkfieldtype.h +++ b/libs/video/renderer/vulkan/vkgen/vkfieldtype.h @@ -17,4 +17,6 @@ -(string)parseType; @end +string parseItemType (PLItem *item); + #endif//__renderer_vulkan_vkgen_vkfieldtype_h diff --git a/libs/video/renderer/vulkan/vkgen/vkfieldtype.r b/libs/video/renderer/vulkan/vkgen/vkfieldtype.r index cb1a04508..5f9429a20 100644 --- a/libs/video/renderer/vulkan/vkgen/vkfieldtype.r +++ b/libs/video/renderer/vulkan/vkgen/vkfieldtype.r @@ -5,6 +5,21 @@ #include "vkgen.h" #include "vktype.h" +string +parseItemType (PLItem *item) +{ + string str = [item string]; + if (str) { + return str; + } + string mask = "QFMultiType"; + for (int i = [item count]; i-- > 0; ) { + str = [[item getObjectAtIndex:i] string]; + mask = mask + " | (1 << " + str + ")"; + } + return mask; +} + @implementation FieldType +fieldType:(PLItem *)item @@ -19,21 +34,6 @@ str_free (parse_type); } -static string -parseItemType (PLItem *item) -{ - string str = [item string]; - if (str) { - return str; - } - string mask = "QFMultiType"; - for (int i = [item count]; i-- > 0; ) { - str = [[item getObjectAtIndex:i] string]; - mask = mask + " | (1 << " + str + ")"; - } - return str_hold (mask); -} - -initWithItem:(PLItem *)item { if (!(self = [super init])) { @@ -47,7 +47,8 @@ parseItemType (PLItem *item) parse_type = [field_type parseType]; parser = str_hold ("parse_" + type); } else { - parse_type = parseItemType([item getObjectForKey:"parse_type"]); + PLItem *typeItem = [item getObjectForKey:"parse_type"]; + parse_type = str_hold (parseItemType(typeItem)); type = str_hold ([[item getObjectForKey:"type"] string]); parser = str_hold ([[item getObjectForKey:"parser"] string]); } diff --git a/libs/video/renderer/vulkan/vkgen/vkhandle.r b/libs/video/renderer/vulkan/vkgen/vkhandle.r index da4a4fbcc..b76d99af4 100644 --- a/libs/video/renderer/vulkan/vkgen/vkhandle.r +++ b/libs/video/renderer/vulkan/vkgen/vkhandle.r @@ -12,10 +12,10 @@ output_handle (string name, PLItem *handle) string create = str_hold ([[handle getObjectForKey:"create"] string]); string custom = str_hold ([[handle getObjectForKey:"custom"] string]); if (!custom) { - fprintf (output_file, "static int parse_%s (const plfield_t *field, const plitem_t *item, void *data, plitem_t *messages, void *context)\n", name); + fprintf (output_file, "static int parse_%s (const plitem_t *item, void **data, plitem_t *messages, parsectx_t *context)\n", name); fprintf (output_file, "{\n"); - fprintf (output_file, "\t__auto_type handle = (%s *) data;\n", name); - fprintf (output_file, "\tvulkan_ctx_t *ctx = ((parsectx_t *) context)->vctx;\n"); + fprintf (output_file, "\t__auto_type handle = (%s *) data[0];\n", name); + fprintf (output_file, "\tvulkan_ctx_t *ctx = context->vctx;\n"); fprintf (output_file, "\tqfv_device_t *device = ctx->device;\n"); fprintf (output_file, "\tqfv_devfuncs_t *dfunc = device->funcs;\n"); fprintf (output_file, "\tif (PL_Type (item) == QFString) {\n"); @@ -47,15 +47,18 @@ output_handle (string name, PLItem *handle) fprintf (output_file, "int parse_%s_handleref (const plfield_t *field, const plitem_t *item, void *data, plitem_t *messages, void *context)\n", name); fprintf (output_file, "{\n"); fprintf (output_file, "\thandleref_t *handleref = data;\n"); + fprintf (output_file, "\tvoid *hrdata[] = { &handleref->handle };\n"); fprintf (output_file, "\thandleref->name = strdup (field->name);\n"); if (custom) { - fprintf (output_file, "\treturn %s (field, item, &handleref->handle, messages, context);\n", custom); + fprintf (output_file, "\treturn %s (item, hrdata, messages, context);\n", custom); } else { - fprintf (output_file, "\treturn parse_%s (field, item, &handleref->handle, messages, context);\n", name); + fprintf (output_file, "\treturn parse_%s (item, hrdata, messages, context);\n", name); } fprintf (output_file, "}\n"); - fprintf (header_file, "static int parse_%s (const plfield_t *field, const plitem_t *item, void *data, plitem_t *messages, void *context);\n", name); + if (!custom) { + fprintf (header_file, "static int parse_%s (const plitem_t *item, void **data, plitem_t *messages, parsectx_t *context);\n", name); + } fprintf (header_file, "int parse_%s_handleref (const plfield_t *field, const plitem_t *item, void *data, plitem_t *messages, void *context);\n", name); str_free (custom); str_free (symtab); diff --git a/libs/video/renderer/vulkan/vkparse.c b/libs/video/renderer/vulkan/vkparse.c index 394a402d9..45236c7ff 100644 --- a/libs/video/renderer/vulkan/vkparse.c +++ b/libs/video/renderer/vulkan/vkparse.c @@ -58,6 +58,7 @@ #include "QF/Vulkan/command.h" #include "QF/Vulkan/instance.h" #include "QF/Vulkan/image.h" +#include "QF/Vulkan/pipeline.h" #include "QF/Vulkan/renderpass.h" #include "QF/Vulkan/shader.h" #include "QF/Vulkan/swapchain.h" @@ -159,6 +160,10 @@ parse_basic (const plfield_t *field, const plitem_t *item, *(uint32_t *) data = VK_SUBPASS_EXTERNAL; } else { ret = !cexpr_eval_string (valstr, &ectx); + if (!ret) { + PL_Message (messages, item, "error parsing %s: %s", + field->name, valstr); + } } //Sys_Printf (" %x\n", *(uint32_t *)data); @@ -224,8 +229,9 @@ parse_single (const plfield_t *field, const plitem_t *item, //Sys_Printf ("parse_single: %s %zd %d %p %p\n", field->name, field->offset, // field->type, field->parser, field->data); - if (PL_Type (item) != single->type) { - PL_Message (messages, item, "error: wrong type"); + if (!PL_CheckType (single->type, PL_Type (item))) { + PL_TypeMismatch (messages, item, field->name, single->type, + PL_Type (item)); return 0; } @@ -309,13 +315,13 @@ parse_string (const plfield_t *field, const plitem_t *item, __auto_type string = (parse_string_t *) field->data; __auto_type value = (char **) ((byte *)data + string->value_offset); - const char *str = PL_BinaryData (item); + const char *str = PL_String (item); - Sys_Printf ("parse_string: %s %zd %d %p %p %p\n", - field->name, field->offset, field->type, field->parser, - field->data, data); - Sys_Printf (" %zd\n", string->value_offset); - Sys_Printf (" %s\n", str); + //Sys_Printf ("parse_string: %s %zd %d %p %p %p\n", + // field->name, field->offset, field->type, field->parser, + // field->data, data); + //Sys_Printf (" %zd\n", string->value_offset); + //Sys_Printf (" %s\n", str); *value = strdup (str); return 1; @@ -350,11 +356,11 @@ parse_RGBA (const plitem_t *item, void **data, } static int -parse_VkShaderModule (const plfield_t *field, const plitem_t *item, void *data, - plitem_t *messages, void *context) +parse_VkShaderModule (const plitem_t *item, void **data, + plitem_t *messages, parsectx_t *context) { - __auto_type handle = (VkShaderModule *) data; - vulkan_ctx_t *ctx = ((parsectx_t *) context)->vctx; + __auto_type handle = (VkShaderModule *) data[0]; + vulkan_ctx_t *ctx = context->vctx; const char *name = PL_String (item); handleref_t *hr = Hash_Find (ctx->shaderModules, name); @@ -367,10 +373,10 @@ parse_VkShaderModule (const plfield_t *field, const plitem_t *item, void *data, } static int -parse_VkShaderModule_resource (const plfield_t *field, const plitem_t *item, - void *data, plitem_t *messages, void *context) +parse_VkShaderModule_resource (const plitem_t *item, void **data, + plitem_t *messages, parsectx_t *context) { - __auto_type handle = (VkShaderModule *) data; + __auto_type handle = (VkShaderModule *) data[0]; vulkan_ctx_t *ctx = ((parsectx_t *) context)->vctx; qfv_device_t *device = ctx->device; @@ -382,6 +388,15 @@ parse_VkShaderModule_resource (const plfield_t *field, const plitem_t *item, return 1; } +static int +parse_VkDescriptorSetLayout_array (const plfield_t *field, + const plitem_t *item, void *data, + plitem_t *messages, void *context) +{ + void *layout[] = { data }; + return parse_VkDescriptorSetLayout (item, layout, messages, context); +} + static const char * handleref_getkey (const void *hr, void *unused) { @@ -494,57 +509,6 @@ static plfield_t renderpass_fields[] = { {} }; -VkRenderPass -QFV_ParseRenderPass (vulkan_ctx_t *ctx, plitem_t *plist) -{ - qfv_device_t *device = ctx->device; - qfv_renderpass_t renderpass_data = {}; - plitem_t *messages = PL_NewArray (); - VkRenderPass renderpass; - exprsym_t var_syms[] = { - {"swapchain", &qfv_swapchain_t_type, ctx->swapchain}, - {"msaaSamples", &VkSampleCountFlagBits_type, &ctx->msaaSamples}, - {} - }; - exprtab_t vars_tab = { var_syms, 0 }; - exprctx_t exprctx = {}; - parsectx_t parsectx = { &exprctx, ctx }; - - exprctx.external_variables = &vars_tab; - exprctx.memsuper = new_memsuper (); - exprctx.messages = messages; - exprctx.hashlinks = &ctx->hashlinks; - - cexpr_init_symtab (&vars_tab, &exprctx); - - if (!PL_ParseStruct (renderpass_fields, plist, &renderpass_data, - messages, &parsectx)) { - for (int i = 0; i < PL_A_NumObjects (messages); i++) { - Sys_Printf ("%s\n", PL_String (PL_ObjectAtIndex (messages, i))); - } - return 0; - } - PL_Free (messages); - delete_memsuper (exprctx.memsuper); - - renderpass = QFV_CreateRenderPass (device, - renderpass_data.attachments, - renderpass_data.subpasses, - renderpass_data.dependencies); - - free (renderpass_data.attachments); - for (size_t i = 0; i < renderpass_data.subpasses->size; i++) { - free ((void *) renderpass_data.subpasses->a[i].pInputAttachments); - free ((void *) renderpass_data.subpasses->a[i].pColorAttachments); - free ((void *) renderpass_data.subpasses->a[i].pResolveAttachments); - free ((void *) renderpass_data.subpasses->a[i].pDepthStencilAttachment); - free ((void *) renderpass_data.subpasses->a[i].pPreserveAttachments); - } - free (renderpass_data.subpasses); - free (renderpass_data.dependencies); - return renderpass; -} - void QFV_ParseResources (vulkan_ctx_t *ctx, plitem_t *pipelinedef) { @@ -609,3 +573,95 @@ QFV_GetEnum (const char *name) { return Hash_Find (enum_symtab, name); } + +VkRenderPass +QFV_ParseRenderPass (vulkan_ctx_t *ctx, plitem_t *plist) +{ + qfv_device_t *device = ctx->device; + qfv_renderpass_t renderpass_data = {}; + plitem_t *messages = PL_NewArray (); + VkRenderPass renderpass; + exprsym_t var_syms[] = { + {"swapchain", &qfv_swapchain_t_type, ctx->swapchain}, + {"msaaSamples", &VkSampleCountFlagBits_type, &ctx->msaaSamples}, + {} + }; + exprtab_t vars_tab = { var_syms, 0 }; + exprctx_t exprctx = {}; + parsectx_t parsectx = { &exprctx, ctx }; + + exprctx.external_variables = &vars_tab; + exprctx.memsuper = new_memsuper (); + exprctx.messages = messages; + exprctx.hashlinks = &ctx->hashlinks; + + cexpr_init_symtab (&vars_tab, &exprctx); + + if (!PL_ParseStruct (renderpass_fields, plist, &renderpass_data, + messages, &parsectx)) { + for (int i = 0; i < PL_A_NumObjects (messages); i++) { + Sys_Printf ("%s\n", PL_String (PL_ObjectAtIndex (messages, i))); + } + return 0; + } + PL_Free (messages); + delete_memsuper (exprctx.memsuper); + + renderpass = QFV_CreateRenderPass (device, + renderpass_data.attachments, + renderpass_data.subpasses, + renderpass_data.dependencies); + + free (renderpass_data.attachments); + for (size_t i = 0; i < renderpass_data.subpasses->size; i++) { + free ((void *) renderpass_data.subpasses->a[i].pInputAttachments); + free ((void *) renderpass_data.subpasses->a[i].pColorAttachments); + free ((void *) renderpass_data.subpasses->a[i].pResolveAttachments); + free ((void *) renderpass_data.subpasses->a[i].pDepthStencilAttachment); + free ((void *) renderpass_data.subpasses->a[i].pPreserveAttachments); + } + free (renderpass_data.subpasses); + free (renderpass_data.dependencies); + return renderpass; +} + +VkPipeline +QFV_ParsePipeline (vulkan_ctx_t *ctx, plitem_t *plist) +{ + qfv_device_t *device = ctx->device; + + plitem_t *messages = PL_NewArray (); + exprctx_t exprctx = {}; + parsectx_t parsectx = { &exprctx, ctx }; + exprsym_t var_syms[] = { + {"msaaSamples", &VkSampleCountFlagBits_type, &ctx->msaaSamples}, + {} + }; + exprtab_t vars_tab = { var_syms, 0 }; + + exprctx.external_variables = &vars_tab; + exprctx.memsuper = new_memsuper (); + exprctx.messages = messages; + exprctx.hashlinks = &ctx->hashlinks; + + cexpr_init_symtab (&vars_tab, &exprctx); + + __auto_type cInfo = QFV_AllocGraphicsPipelineCreateInfoSet (1, alloca); + memset (&cInfo->a[0], 0, sizeof (cInfo->a[0])); + + if (!parse_VkGraphicsPipelineCreateInfo (0, plist, &cInfo->a[0], + messages, &parsectx)) { + for (int i = 0; i < PL_A_NumObjects (messages); i++) { + Sys_Printf ("%s\n", PL_String (PL_ObjectAtIndex (messages, i))); + } + return 0; + } + PL_Free (messages); + delete_memsuper (exprctx.memsuper); + + cInfo->a[0].renderPass = ctx->renderpass.renderpass; + __auto_type plSet = QFV_CreateGraphicsPipelines (device, 0, cInfo); + VkPipeline pipeline = plSet->a[0]; + free (plSet); + return pipeline; +} diff --git a/libs/video/renderer/vulkan/vkparse.h b/libs/video/renderer/vulkan/vkparse.h index e4fe36774..394cd296d 100644 --- a/libs/video/renderer/vulkan/vkparse.h +++ b/libs/video/renderer/vulkan/vkparse.h @@ -1,17 +1,17 @@ #ifndef __vkparse_h #define __vkparse_h +typedef struct parsectx_s { + struct exprctx_s *ectx; + struct vulkan_ctx_s *vctx; +} parsectx_t; + #include "QF/cexpr.h" #include "QF/Vulkan/renderpass.h" #ifdef vkparse_internal #include "libs/video/renderer/vulkan/vkparse.hinc" #endif -typedef struct parsectx_s { - struct exprctx_s *ectx; - struct vulkan_ctx_s *vctx; -} parsectx_t; - typedef struct parseres_s { const char *name; plfield_t *field; @@ -23,9 +23,11 @@ typedef struct handleref_s { uint64_t handle; } handleref_t; -VkRenderPass QFV_ParseRenderPass (vulkan_ctx_t *ctx, plitem_t *plist); void QFV_ParseResources (vulkan_ctx_t *ctx, plitem_t *plist); void QFV_InitParse (vulkan_ctx_t *ctx); exprenum_t *QFV_GetEnum (const char *name); +VkRenderPass QFV_ParseRenderPass (vulkan_ctx_t *ctx, plitem_t *plist); +VkPipeline QFV_ParsePipeline (vulkan_ctx_t *ctx, plitem_t *plist); + #endif//__vkparse_h diff --git a/libs/video/renderer/vulkan/vkparse.plist b/libs/video/renderer/vulkan/vkparse.plist index e270fb9be..97e767676 100644 --- a/libs/video/renderer/vulkan/vkparse.plist +++ b/libs/video/renderer/vulkan/vkparse.plist @@ -131,12 +131,8 @@ string = pName; }; module = { - type = (single, { - parse_type = QFString; - type = VkShaderModule; - parser = parse_VkShaderModule; - }); - value = module; + type = (custom, QFString, parse_VkShaderModule); + fields = (module); }; specializationInfo = { type = (single, VkSpecializationInfo); @@ -253,7 +249,7 @@ type = (array, { parse_type = (QFDictionary, QFString); type = VkDescriptorSetLayout; - parser = parse_VkDescriptorSetLayout; + parser = parse_VkDescriptorSetLayout_array; }); size = setLayoutCount; values = pSetLayouts; @@ -312,12 +308,9 @@ value = pDynamicState; }; layout = { - type = (single, { - parse_type = (QFDictionary, QFString); - type = VkPipelineLayout; - parser = parse_VkPipelineLayout; - }); - value = layout; + type = (custom, (QFDictionary, QFString), + parse_VkPipelineLayout); + fields = (layout); }; basePipelineHandle = { type = (custom, QFString, parse_BasePipeline); diff --git a/libs/video/renderer/vulkan/vulkan_vid_common.c b/libs/video/renderer/vulkan/vulkan_vid_common.c index af24c78eb..179748dfe 100644 --- a/libs/video/renderer/vulkan/vulkan_vid_common.c +++ b/libs/video/renderer/vulkan/vulkan_vid_common.c @@ -417,6 +417,15 @@ Vulkan_DestroyRenderPass (vulkan_ctx_t *ctx) void Vulkan_CreatePipelines (vulkan_ctx_t *ctx) { + qfv_load_pipeline (ctx); + + plitem_t *item = ctx->pipelineDef; + if (!item || !(item = PL_ObjectForKey (item, "pipeline"))) { + Sys_Printf ("error loading pipeline\n"); + } else { + Sys_Printf ("Found pipeline def\n"); + } + ctx->pipeline = QFV_ParsePipeline (ctx, item); } void