Harlequin RIP SDK
Starting the Harlequin RIP

Starting the Harlequin RIP is not a difficult process. However, the "clrip" application layer may not be the best example to use to understand this process, because it has the competing demands of being a production application, and also a robust testbed for build feature integration, regression testing, fault extraction and performance measurement. The "minrip" application layer demonstrates this process in its most reduced form, so may not show all of the nuance required to understand the entire process.

When your application layer is linked to the Harlequin RIP SDK, there are a few required steps that must be taken before the RIP can be started. The required steps are:

  1. Setting the DLL load path (Windows only)
  2. Load the pthreads DLL (Windows only)
  3. Setting the threading API pointer
  4. Starting the SDK
  5. Starting the RIP

The "clrip" application adds some other optional steps into this process. The following sections will explain both the optional and mandatory steps, in the order they are performed, up to the point that the RIP is started and is ready to process jobs.

Setting the DLL load path (Windows only)

On Windows, Global Graphics recommends delay-loading the core library (see Linking corelib). This allows the DLL load path to be modified before loading the core library. The "clrip" application layer uses the function extendLibrarySearchPath() to modify the PATH environment variable to search a directory relative to the location of the clrip application executable. The "minrip" application layer uses the function add_dll_path() to do the same. You may choose to keep these functions, or modify them to specify your preferred deployment location for the RIP DLLs.

Connect assert and trace handlers (optional)

The Harlequin RIP has an assert and trace system that is used in test builds to test for invalid code states. Neither the assert nor trace handler functions will be called unless the RIP is built with assertions enabled. This is not normally the case for customer distributions, but Global Graphics may provide assertion enabled builds on occasion to help diagnose difficult problems. Assertion enabled builds should not be used in production.

You may want to connect these handlers to your own error-reporting systems. The SDK library function SwLeSetHqAssertHandlers() can be called at this point to set the handlers that will be used if asserts are enabled in the RIP.

It is possible to rebuild the skin (Harlequin SDK library and application layers) with assertions enabled, even if the core library does not have assertions enabled. See Harlequin assert and trace handlers for details.

Load the pthreads DLL (Windows only)

On Windows, Global Graphics uses the "Pthreads-win32" library to implement the Pthread interface. This is supplied in the distribution as the dynamic link library pthreads.dll. The SDK provides the function load_pthreads_dll() to explicitly load this library, and to read and store the required function addresses from it. load_pthreads_dll() is provided on Linux and MacOS too, but on those platforms it is a stub function that does nothing.

The pthreads library must be loaded before the Harlequin SDK or RIP are initialized. It should be loaded after setting the DLL load path.

If the load_pthreads_dll() function fails, the application should abort immediately. The Harlequin SDK and RIP will not function without it.

char *error_string ;
if ( !load_pthreads_dll(NULL, &error_string) ) {
// ...error message here...
return EXIT_FAILURE
}
HqBool load_pthreads_dll(const char *dll_name, char **error_string)
Load the pthreads library.
Definition: ggsthreads.c:185
#define NULL
Definition of NULL pointer.
Definition: hqtypes.h:37

Setting the threading API pointer

The Harlequin RIP and SDK call pthreads functions through an API registered in RDR (see Threads interface). Immediately after loading the pthreads library, and before initializing the SDK, the application should update the pthreads API pointer. This can be done by calling the function get_skin_api_ptr(), which the SDK provides to retrieve API pointers from RDR. The get_skin_api_ptr() function has special logic to allow it to get the pthreads API even before the SDK is initialized. If this function fails, the application should abort immediately. The Harlequin SDK and RIP will not function without it.

if ( !get_skin_api_ptr(RDR_API_PTHREADS, PTHREAD_API_VERSION, &pthread_api) ) {
// ...error message here...
return EXIT_FAILURE
}
HqBool get_skin_api_ptr(sw_rdr_type apitype, sw_rdr_id id, void *papi)
Get an API pointer to a specified version of an RDR_CLASS_API resource.
Definition: rdrglue.c:289
@ RDR_API_PTHREADS
Definition: apis.h:48

Set up last-resort exception catcher (optional)

The last-resort exception catcher catches exceptions that abort the application, and logs stack traces of the thread states at the point of the crash. An exception catcher function can be registered by calling HqCatchExceptions() to output these stack traces. The information in these stack traces may be useful for diagnosing the root cause of when reporting a problem to Global Graphics. You may want to connect this to your own error-reporting system.

Set the main thread name (optional)

After pthreads has been loaded and the threading API pointer is set, you can call the function set_thread_name() to change the name of the main application thread. This may be useful when debugging, and also for the stack traces produced by the last-resort exception catcher.

Start the timing and profiling subsystem (optional)

The Harlequin RIP has a timing and profiling subsystem, used to extract high-frequency performance data about the internal operation of the Harlequin RIP. This system is not normally built into case for customer distributions, but Global Graphics may provide timing enabled builds on occasion to help diagnose difficult problems.

The timing and profiling system is started by calling SwLeProbeLogInit().

Starting the SDK

The preferred method to start the SDK is to use SwLeDo(). This function takes an operation to perform and a parameters structure. The parameters structure should be initialized before the first call to SwLeDo(), and then the same parameters structure should be passed to all subsequent calls to SwLeDo():

SwLeDoParams params = {
.size = sizeof(SwLeDoParams),
.RIP_maxAddressSpaceInBytes = 0,
.RIP_workingSizeInBytes = 0, // set your working memory here
.RIP_emergencySizeInBytes = 0, // set your emergency size here
.monitor = monitorCallback,
.fContinueAfterError = TRUE,
.fProcessQueueWait = TRUE,
} ;
struct SwLeDoParams SwLeDoParams
A structure containing the parameters for SwLeDo().
static void monitorCallback(uint32 bufLen, const uint8 *u8buf)
Monitor information callback.
Definition: main.c:1255
#define TRUE
HqBool boolean true value.
Definition: hqtypes.h:508
A structure containing the parameters for SwLeDo().
Definition: skinkit.h:848
size_t size
Definition: skinkit.h:849

To start the SDK, the operation SW_LE_DO_SDK_START should be used:

int32 result = SwLeDo(SW_LE_DO_SDK_START, &params) ;
if ( result != 0 ) {
// ...error cleanup and message...
return EXIT_FAILURE ;
}
int32 SwLeDo(int32 op, SwLeDoParams *params)
Perform required operations to reach the requested SDK and RIP state.
Definition: skinkit.c:1393
@ SW_LE_DO_SDK_START
Definition: skinkit.h:746
signed int int32
32-bit signed integer
Definition: hqtypes.h:122

Even though SwLeDo() can perform multiple operations at once, it is normal to stop after starting the SDK. Some configuration operations, such as setting the number of rendered threads, may only be performed at this point. Some other configuration operations, such as registering devices, backends and APIs are better performed at this time, before the RIP starts processing jobs.

The SW_LE_DO_SDK_START operation initializes the SDK runtime and then starts it. You may wish to separate these steps if you want to install a RIP exit callback to determine the reason for failed RIP startup. To do this, call SwLeDo() with SW_LE_DO_INITRUNTIME, install the callback, and then call SwLeDo() with SW_LE_DO_SDK_START:

int32 result = SwLeDo(SW_LE_DO_INITRUNTIME, &params) ;
HQASSERT(result == 0, "This should not fail") ;
SwLeSetRipExitFunction(exit_callback) ;
result = SwLeDo(SW_LE_DO_SDK_START, &params) ;
if ( result != 0 ) {
// ...error cleanup and message...
return EXIT_FAILURE ;
}
#define HQASSERT(fCondition, pszMessage)
Definition: hqassert.h:200
void SwLeSetRipExitFunction(SwLeRIPEXITCALLBACK *pfnRipExit)
Supply a callback function to be called when the RIP exits.
Definition: skinkit.c:903
@ SW_LE_DO_INITRUNTIME
Definition: skinkit.h:722

Starting the SDK using low-level functions

Older integrations may use the control flow functions individually. Global Graphics recommends the use of SwLeDo(), because it makes it easy to perform multiple initialization or processing steps in one call, and it passed parameters between processing steps correctly and handles failure cleanup gracefully, it is possible to call control flow functions directly. Calling SwLeDo() with SW_LE_DO_SDK_START for the first time is equivalent to calling SwLeInitRuntime() followed by SwLeSDKStart().

Update API pointers (dynamic SDK library only)

The Harlequin RIP and SDK call APIs registered in RDR through pointers to structures of function pointers. If the SDK library is compiled as a static library, starting the SDK takes care of finding and storing the pointers it requires in variables, that are then exported from the SDK library to the application layer. If the SDK library is built is built as a dynamic library, the application layer needs to have its own copy of all of the API pointers required. These pointers need to be retrieved after the SDK has been initialized, and the APIs have been registered.

The "clrip" and "minrip" application layers each define a different set of API pointers that they use, and their own version of the update_api_pointers() function to perform this task. These functions just call get_skin_api_ptr() to update the application layer's pointer for each API that the application skin uses. This function is stubbed out when the SDK library is built and linked as a static library.

The SDK library should not be built or linked as a dynamic library without Global Graphics' approval. In particular, this is only suitable for RIPs secured using the "LDK" method, and not for RIPs secured using the "LE" method.

Register extra devices, raster backends, APIs (optional)

After the SDK is started, RIP parameters such as the number of renderer threads can be set, custom devices can be added, event handlers, APIs, module instances, and raster backends can be registered, and the input queue can even be manipulated.

Note that you cannot perform any operations that allocate from the RIP's memory pools at this point, or require the RIP to have initialized its state. This includes calling SwRegisterCMM() and SwRegisterHTM() to register color management modules or halftone modules. It is possible to register these modules directly in RDR at this point, but you will not get the extra validation checks that SwRegisterCMM() and SwRegisterHTM() perform on the modules. Color management and halftone modules can be registered using these functions after starting the RIP.

Typical operations performed at this point are the following:

Set the number of RIP threads (optional)

The number of threads that the RIP should use for work (subject to licensing restrictions) can be set using SwLeSetRipRendererThreads().

Select raster output

If there is only one raster output backend used by your application, you can set it at this time be calling SwLeSetRasterAPI().

If your application supports multiple raster output backends, it is better to do the same as the "clrip" application layer, and allow selection of raster backends based on the page device's /PageBufferType parameter.

If your application supports multiple raster output backends, the method recommended to is to select the raster backend based on the page device's /PageBufferType parameter. To implement this selection method, the application layer should provide a function to find the raster backend and call SwLeSetRasterAPI() based on a parameter value, and register a callback hook for the pagebuffer device at this point in startup:

static HqBool RIPCALL PageBufferSelect(void *pJobContext, const void *param)
{
// ... select api based on *param ...
return TRUE ;
}
// ... in code called after starting the SDK:
SwLePgbSetCallback("PageBufferType", ParamString, PageBufferSelect) ;
void SwLeSetRasterAPI(sw_raster_api_20230105 *api)
Sets the API structure that the RIP will call back to present raster data to the skin.
Definition: skinkit.c:921
int HqBool
Harlequin standard boolean type.
Definition: hqtypes.h:502
@ ParamString
DEVICEPARAM is string.
Definition: swdevice.h:1320
HqBool SwLePgbSetCallback(const char *paramname, int32 paramtype, SwLeParamCallback *pfnPGBCallback)
Sets a callback functions for the named pagebuffer device parameter.
Definition: pgbdev.c:623
#define RIPCALL
The normal calling convention for RIP-exported APIs.
Definition: ripcall.h:27
A structure collecting the raster backend API functions.
Definition: rasthand.h:492

With such a raster backend selection method in place, you can register the raster backend implementations you want to support in RDR at this time:

static sw_raster_api_20230105 my_raster_api = {
.blank_page_fn = My_BlankPage,
.raster_start_fn = My_RASTER_start,
.raster_write_data_fn = My_RASTER_write_data,
.raster_finish_fn = My_RASTER_finish,
} ;
CSTRING_AND_LENGTH("Mine"), 20230105,
&my_raster_api, sizeof(my_raster_api),
// ...cleanup and return error...
}
sw_rdr_result SwRegisterNamedRDR(sw_rdr_namespace rdrspace, const char *name, size_t namelen, sw_rdr_id rdrid, void *ptr, size_t length, sw_rdr_priority priority)
Register a Named RDR.
@ SW_RDR_SUCCESS
Definition: rdrapi.h:617
@ RDR_NAMES_RASTERAPI
Definition: rdrapi.h:564
@ SW_RDR_NORMAL
Definition: rdrapi.h:610
#define CSTRING_AND_LENGTH(s1_)
Definition: std.h:156
SwLeBLANKPAGECALLBACK * blank_page_fn
Definition: rasthand.h:531

Add custom devices (optional)

One or more custom device types may be registered using SwLeAddCustomDevices(). Note that adding a custom device type implementation does not create any instances of the device type: this can only be performed after starting the RIP.

Register event handlers (optional)

Event handlers can be installed at this point, especially timeline handlers that are used to monitor the entire RIP lifetime, or job lifetimes. Since the RIP is not yet running, handlers registered at this point will be guaranteed to see both the start and end of any timeline created by the RIP:

static sw_event_handlers skin_job_handlers[] = {
{ skin_job_start, NULL, 0, EVENT_TIMELINE_START, SW_EVENT_NORMAL },
{ skin_job_end, NULL, 0, EVENT_TIMELINE_ENDED, SW_EVENT_DEFAULT },
};
if ( SwRegisterHandlers(skin_job_handlers,
NUM_ARRAY_ITEMS(skin_job_handlers)) != SW_RDR_SUCCESS )
return FALSE ;
sw_rdr_result SwRegisterHandlers(sw_event_handlers *handlers, int count)
Register multiple Event Handlers atomically.
@ SW_EVENT_NORMAL
Definition: eventapi.h:564
@ SW_EVENT_DEFAULT
Definition: eventapi.h:563
#define NUM_ARRAY_ITEMS(_array_)
Definition: std.h:144
#define FALSE
HqBool boolean false value.
Definition: hqtypes.h:512
@ EVENT_TIMELINE_ENDED
The Timeline has ended.
Definition: timelineapi.h:637
@ EVENT_TIMELINE_START
The Timeline has started.
Definition: timelineapi.h:581
@ EVENT_TIMELINE_ABORTED
The Timeline has aborted.
Definition: timelineapi.h:670
Atomic multiple Handler registration.
Definition: eventapi.h:759

Timeline handlers can be used to provide asynchronous notification of important events in the RIP and job lifecycle.

Starting the RIP

The RIP itself is started up on a separate thread, allowing the caller to feed data to the RIP on a procedural basis, with callbacks being used to receive output data from the RIP (both raster data and monitor messages).

The RIP is started by calling SwLeDo() with the SW_LE_DO_RIP_START operation, and the same parameter structure that was used to start the SDK.

int32 result = SwLeDo(SW_LE_DO_RIP_START, &params) ;
if ( result != 0 ) {
// ...error cleanup and message...
}
@ SW_LE_DO_RIP_START
Definition: skinkit.h:777

Starting the RIP using low-level functions

Older integrations may use the control flow functions individually. Calling SwLeDo() with SW_LE_DO_RIP_START will ensure that the functions SwLeMemInit() and then SwLeStart() are called as necessary, and in the right order. SwLeDo() will also ensure that the anti-aliasing module is registered in between these calls, allowing use of the /ResamplingFactor pagedevice key to downsample raster output.

Register CMM and Halftone module instances (optional)

After the RIP has been started you can register color management module instances using SwRegisterCMM(), and halftone module instances using SwRegisterHTM(). These functions perform extra validation on the module instances, to ensure they implement all of the required callbacks.

sw_cmm_api my_module = { ... } ;
if ( SwRegisterCMM(&my_module) != SW_API_REGISTERED ) {
// ...error cleanup and message...
}
@ SW_API_REGISTERED
Module instance successfully registered.
Definition: swapi.h:33
sw_api_result SwRegisterCMM(sw_cmm_api *implementation)
This routine makes an alternate CMM implementation known to the rip.
The definition of an implementation of the alternate CMM interface.
Definition: swcmm.h:723

Use the sw_htm_api type and SwRegisterHTM() for halftone modules instead of sw_cmm_api and SwRegisterCMM() in the example above.

It is possible to register CMM and halftone modules earlier than this, by partially initializing the RIP state before starting it using SwLeDo(), or equivalently by calling SwLeMemInit() before calling SwRegisterCMM() or SwRegisterHTM():

int32 result = SwLeDo(SW_LE_DO_RIP_MEMINIT, &params) ;
if ( result != 0 ) {
// ...error cleanup and message...
}
// Register module using SwRegisterCMM() or SwRegisterHTM()...
result = SwLeDo(SW_LE_DO_RIP_START, &params) ;
if ( result != 0 ) {
// ...error cleanup and message...
}
@ SW_LE_DO_RIP_MEMINIT
Definition: skinkit.h:767

Color management and halftone modules can registered directly in RDR immediately after starting the SDK, but this does not perform the extra validation checks that SwRegisterCMM() and SwRegisterHTM() perform. If your module does not implement all of the required callbacks, the RIP may fail when it uses the module.

// ... SwLeDo(SW_LE_DO_SDK_START, &params) ...
sw_cmm_api my_module = { ... } ;
CSTRING_AND_LENGTH("Mine"), 0,
&my_module, sizeof(my_module),
// ...cleanup and return error...
}
@ RDR_NAMES_CMMAPI
Definition: rdrapi.h:561

Use sw_htm_api type and RDR_NAMES_HTMAPI for halftone modules instead of sw_cmm_api and RDR_NAMES_CMMAPI in the example above.

Set the control-C interrupt handler (optional)

An application interrupt handler can be installed after starting the RIP. Installing the handler should wait until after starting the RIP to ensure that SIGINT and SIGTERM only get caught by the main thread, and not the RIP threads. The application interrupt handler can be installed using PKSetCtrlCHandler().

Setting the RIP base configuration (optional)

After the RIP is started, jobs can be submitted to it. A common practice at this point is to submit a configuration job to the RIP, which will modify the default RIP configuration settings. A configuration job can change the RIP configuration in a manner that persists until the RIP is shut down or restarted.

Setting the RIP base configuration often include mounting all drives available on a machine, and possibly also mounting instances of custom devices added after starting the SDK.

Configuration jobs can be submitted using the inputs API, by calling inputq_print_job() with no filename, but with a PostScript configuration override buffer. If submitted this way, the job will only be processed when the job processing loop is started. Configuration jobs submitted in this way may be placed at the start of the input queue to ensure processing before user jobs by specifying position 0 on the queue.

Alternatively, configuration jobs submitted at this time may be run immediately by constructing the configuration PostScript, and calling SwLeJobStart() followed by SwLeJobEnd(). The SkinDynamicBuffer extendable buffer type is useful when constructing configuration jobs to run in this way:

// Add config with SkinDynamicBufferAdd(&config, "PS config stuff", ...)
// Configuration job data must end by leaving false on the PS stack:
if ( !SkinDynamicBufferAdd(&mount, "false") )
return FALSE;
HqBool ok = SwLeJobStart((uint32)config.used, config.data, NULL) && SwLeJobEnd() ;
if ( !ok ) {
// ...error return...
}
#define SKIN_DYNAMIC_BUFFER_INIT
Auto and static scope initialiser for configuration data buffers.
Definition: skinkit.h:1032
HqBool SkinDynamicBufferAdd(SkinDynamicBuffer *buffer, const char *format,...)
Helper function to write formatted string (using swncopyf()) to the end of a dynamic buffer,...
Definition: skinkit.c:1086
HqBool SwLeJobStart(uint32 cbBuffer, uint8 *pBuffer, void *pJobContext)
Low-level function to prepare the Harlequin RIP to receive a PostScript language job.
Definition: skinkit.c:973
HqBool SwLeJobEnd(void)
Low-level function to terminate the current job.
Definition: skinkit.c:1032
void SkinDynamicBufferFree(SkinDynamicBuffer *buffer)
Free any memory allocated for dynamic buffer data.
Definition: skinkit.c:1077
unsigned int uint32
32-bit unsigned integer
Definition: hqtypes.h:126
Dynamically-allocated buffer structure used to collect configuration PostScript for a job.
Definition: skinkit.h:1026

This method of submitting configuration jobs must not be used once the job processing loop is started.

The "clrip" application layer constructs a configuration job to mount drives and extra devices in the MountAllDrives() function, and runs it immediately using the SwLeJobStart() and SwLeJobEnd() method.

Note that any timeline handlers installed already will receive events for any configuration jobs you submit to the RIP at this point. Configuration jobs will normally have job number 0 (zero). The job number will only start incrementing when it is explicitly set using SwLeSetJobNumber(), or jobs from the input queue are processed. This can be used to filter out configuration jobs from log reporting, if desired.

Process jobs

At this point the RIP is ready to process jobs. How it does this is explained in Processing jobs.

How the "clrip" application layer starts the RIP

The "clrip" application layer interleaves the initialization steps above with two passes over the command-line argument parsing. It uses two passes over the argument list because some of the parameters affect the how the SDK and RIP are started, and so need to be extracted early, and some parameters control processes that happen after the SDK and RIP are started. The first pass over the command-line arguments is made with a flag indicating the SDK is not ready, the SDK is then initialized, and then the second pass is made with the flag indicating the SDK is ready. After the second pass, the RIP or the appropriate Scalable RIP server loop is started, and then if necessary the job processing loop is entered.