Render Drawings in Separate Threads

Evgeniy Tikhonov

April 15, 2021

Introduction

This topic describes how to render drawings in separate threads to improve performance.

ODA BimRv SDK provides a simple API for invoking multithreading functionality for the OdBmMTDrawContext, OdBmMTDraw and OdBmMTDrawingMgr classes.

The OdBmMTDraw class provides thread-safe rendering functionality for a single OdBmDBDrawing element via a specified context.

The OdBmMTDrawingMgr class provides thread-safe rendering functionality for an array of OdBmDBDrawing elements via a specified context. It simply starts a number of OdBmMTDraw instances to render the array of drawings in separate concurrent threads.

You also need an instance of OdBmMTDrawContext or its descendant. See the typedefs and the constructor below:


typedef std::function<OdGsDevicePtr(const OdBmMTDrawContext*)> OdBmCreateGsDeviceFuncType;
typedef std::function<void(OdBmObjectId, OdGsDevice*, const OdBmMTDrawContext*)> OdBmPostProcessFuncType;

OdBmMTDrawContext(
    OdMutex* pDbAccessMutex,
    long picWidth,
    long picHeight,
    bool bZoomToExtents,
    const OdBmCreateGsDeviceFuncType& deviceCreator,
    const OdBmPostProcessFuncType& postProcessor)

The last two parameters are references to functions that need to be implemented.

The first function should satisfy the OdBmCreateGsDeviceFuncType prototype, create an appropriate GS device, and return a pointer to it.

The second function should satisfy the OdBmPostProcessFuncType prototype and perform post-processing of the rendering results.

Example

The following example renders DBDrawings via a GLES2 device and stores the results in a bitmap array. You can find the following code in the BmMTDrawingMgrEx example.

First, create a descendant of the OdBmMTDrawContext class.

struct OdBmGLES2MTDrawContext : public OdBmMTDrawContext {
  OdBmGLES2MTDrawContext(
    OdMutex* dbAccessMutex,
    long picWidth,
    long picHeight,
    bool bZoomToExtents,
    const OdBmCreateGsDeviceFuncType& createGsDeviceFunc,
    const OdBmPostProcessFuncType& postProcessFunc,
    // discard back faces wile rendering
    bool bDiscardBackFaces,
    // use plot generation
    bool bPlotGeneration,
    // background color of result images
    ODCOLORREF bgColor = ODRGB(255, 255, 255)):
  OdBmMTDrawContext(
    dbAccessMutex, picWidth, 
    picHeight, bZoomToExtents,
    createGsDeviceFunc, postProcessFunc),
    discardBackFaces(bDiscardBackFaces),
    plotGeneration(bPlotGeneration),
    bgColor(bgColor) {
  }

  bool discardBackFaces;
  ODCOLORREF bgColor;
  bool plotGeneration;
};

Next, define a cover class, for example:

class OdBmMTRenderer {
public:
  // type defenition for rendering results
  typedef std::map Renditions; 
  /* execution params
    param drawings - an array of drawings to be rendered.
    param picWidth - width of result bitmaps in pixels.
    param picHeight - height of result bitmaps in pixels.
    param bZoomToExtents - use zoom to extents.
    param bDiscardBackFaces - discard back faces while rendering.
    param bgColor - result bitmaps background color.
  */
  Renditions run(OdBmDBDrawingPtrArray& drawings,
    long picWidth, long picHeight, bool bZoomToExtents = false,
    bool bDiscardBackFaces = false, ODCOLORREF bgColor = ODRGB(255, 255, 255));
};

Now, implement it. First, get all the required include files:

#include "BmMTRenderer.h"

#include "DynamicLinker.h"
#include "RxVariantValue.h"
#include "Gi/GiRasterWrappers.h"
#include "Database/MTDrawingMgr/BmMTDrawingMgr.h"

#include "MTDrawingMgr/BmMTDraw.h"
#include "BmGLES2MTDrawContext.h"

Next, implement the run() function. It is a specialized process, so the first step is to create a function of the OdBmCreateGsDeviceFuncType type.

/******************************************************************/
/* GS device creation function                                    */
/******************************************************************/

OdMutex m_createDevice;
auto createDevice = [&m_createDevice](const OdBmMTDrawContext* pContext) {
    TD_AUTOLOCK(m_createDevice);
    const OdBmGLES2MTDrawContext* pToImgContext =
      static_cast(pContext);

    OdGsModulePtr pGsModule = ::odrxDynamicLinker()->loadModule(OdWinGLES2ModuleName);
    OdGsDevicePtr pGsDevice = pGsModule->createBitmapDevice();

    // Enable HLR for Bitmap and WinGDI vectorizers
    OdRxDictionaryPtr pProps = pGsDevice->properties();
    if (pProps->has(OD_T("EnableSoftwareHLR"))) // Check if property is supported
      pProps->putAt(OD_T("EnableSoftwareHLR"), OdRxVariantValue(true));
    if (pProps->has(OD_T("DiscardBackFaces"))) // Check if property is supported
      pProps->putAt(OD_T("DiscardBackFaces"), OdRxVariantValue(pToImgContext->discardBackFaces()));

    OdRxThreadPoolServicePtr pThreadPool = ::odrxDynamicLinker()->loadApp(OdThreadPoolModuleName);
    if (pThreadPool.isNull())
      throw(L"ThreadPool.tx not found.\n");

    OdUInt16 nCPUs = pThreadPool->numCPUs();
    if (pGsDevice->properties()->has(OD_T("MaxRegenThreads")))
      pGsDevice->properties()->putAt(OD_T("MaxRegenThreads"), OdRxVariantValue(nCPUs));

    if (pGsDevice->properties()->has(OD_T("EnableMultithread")))
      pGsDevice->properties()->putAt(OD_T("EnableMultithread"), OdRxVariantValue(true));

    pGsDevice->setLogicalPalette(
      (ODGETRED(pToImgContext->bgColor()) < 140) && (ODGETGREEN(pToImgContext->bgColor()) < 140) && (ODGETBLUE(pToImgContext->bgColor()) < 140) ?
      (::odcmAcadDarkPalette()) : (::odcmAcadLightPalette()), 256);
    pGsDevice->setBackgroundColor(pToImgContext->bgColor());

    return pGsDevice;
};

Next, create a function of the OdBmPostProcessFuncType type.

/******************************************************************/
/* Rendering results processing function                          */
/******************************************************************/

OdMutex m_mutex;
Renditions renditions;
auto postPrecess = [&m_mutex, &renditions](OdBmObjectId id, OdGsDevice* pDevice, const OdBmMTDrawContext* pContext) {
    TD_AUTOLOCK(m_mutex);
    // Create clone of rendered raster image to correctly release all vectorizer resources in current thread
    OdGiRasterImagePtr pRaster = OdGiRasterImageHolder::createObject(
      OdGiRasterImagePtr(pDevice->properties().get()->getAt(OD_T("RasterImage"))));
    renditions[id] = pRaster;
};

Finally, create an instance of OdBmMTDrawContext:

/******************************************************************/
/* Instantiate the context and start MT rendering                 */
/******************************************************************/

OdBmGLES2MTDrawContext m_ctx(&m_mutex, picWidth, picHeight, bZoomToExtents,
    (OdBmMTDrawContext::OdBmCreateGsDeviceFuncType)createDevice, 
    (OdBmMTDrawContext::OdBmPostProcessFuncType)postPrecess,
    bDiscardBackFaces, bgColor);
 
OdBmMTDrawingMgr().draw(drawings, &m_ctx);

return renditions;

You can find the run() method implemented in the BimRv/Examples/BmMTDrawingMgrEx/BmMTDrawingMgrEx.cpp file.