Close

Relief for ODA Team in Ukraine

Learn more
ODA BimRv SDK
Render Drawings in Separate Threads

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.


class OdBmGLES2MTDrawContext : public OdBmMTDrawContext {
public:
  OdBmGLES2MTDrawContext(
    OdMutex* dbAccessMutex,
    long picWidth,
    long picHeight,
    bool bZoomToExtents,
    const OdBmCreateGsDeviceFuncType& createGsDeviceFunc,
    const OdBmPostProcessFuncType& postProcessFunc,
    bool bDiscardBackFaces,
    bool bPlotGeneration,
    ODCOLORREF bgColor = ODRGB(255, 255, 255));   
   /** \details
    Returns true if back faces are discarded.
  */
  bool discardBackFaces() const;
   /** \details
    Returns background color of the rendered raster images.
  */
  ODCOLORREF bgColor() const;
   /** \details
    Returns true if plot generation is set.
  */
  bool plotGeneration() const;
 private:
  bool m_discardBackFaces;
  ODCOLORREF m_bgColor;
  bool m_plotGeneration;
};

Next, define a cover class, for example:.


/** \details
  A helper class to render DB in a separate cuncurent threads.
*/
class OdBmMTRenderer {
public:
  typedef std::map<OdBmObjectId, OdGiRasterImagePtr> Renditions;
     /** \details
    Runs rendering queue.
     \param drawings [in] An array of drawings to be rendered.
    \param picWidth [in] Width of the resulting bitmaps in pixels.
    \param picHeight [in] Height of the resulting bitmaps in pixels.
    \param bZoomToExtents [in] A flag that states whether to use zoom to extents.
    \param bDiscardBackFaces [in] A flag that states whether to discard back faces while rendering.
    \param bgColor [in] The resulting 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<const OdBmGLES2MTDrawContext*>(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);

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

See Also

Rendering in Separate Threads Example

Family Modification and Import.

Unit Objects in BimRv SDK.

Copyright © 2002 – 2022. Open Design Alliance. All rights reserved.