GeUserArea lag in R21 versus R20



  • Hi,

    It seems the changes related to the R21 GUI have introduced quite a lag in the messaging system to draw GeUserArea. I have this following dummy plugin, consisting of a DescriptionTool and a GeUserArea which is running fine on R20, but has quite some lag when the same code is running in R21. The R20 version of the plugin is even a debug build, while the R21 one is a release, just to make sure ...

    Main.cpp

    
    // ========================
    // Cinema 4D C++ plugin
    //
    // PluginName: Test
    // Dummy "empty" plugin
    // ========================
    
    // Main.cpp
    
    #include "c4d.h"
    
    #include "MyUserArea.h"
    #include "MyDescriptionTool.h"
    
    // ====================================
    // Plugin Main 
    // ====================================
    Bool PluginStart(void)
    {
    	ApplicationOutput("Test"_s);
    
    	RegisterCommandPlugin(MYCOMMAND_PLUGIN_ID, "Test"_s, 0, AutoBitmap("icon.png"_s), "Test"_s, NewObjClear(MyCommand));
    	RegisterToolPlugin(MYDESCRIPTIONTOOL_PLUGIN_ID, "MyDescriptionTool"_s, 0, AutoBitmap("icon.png"_s), "MyDescriptionTool"_s, NewObjClear(MyDescriptionTool));
    
    	return true;
    }
    void PluginEnd(void)
    {
    }
    Bool PluginMessage(Int32 id, void * data)
    {
    	switch (id) {
    	case C4DPL_INIT_SYS:
    		if (!g_resource.Init())
    			return false;
    		return true;
    	case C4DMSG_PRIORITY:
    		return true;
    	case C4DPL_BUILDMENU:
    		break;
    	case C4DPL_ENDACTIVITY:
    		return true;
    	}
    	return false;
    }
    

    UserArea.h

    // MyUserArea.h
    
    #include "c4d.h"
    #include "lib_clipmap.h"
    
    // Dummy IDs - for demonstration purposes only
    #define MYCOMMAND_PLUGIN_ID	1000000
    #define MYDIALOG_PLUGIN_ID	1000001
    
    // gadget IDs
    #define IDC_USERAREA		50000
    
    // ====================================
    // GeUserArea
    // ====================================
    
    class MyUserArea : public GeUserArea
    {
    	INSTANCEOF(MyUserArea, GeUserArea)
    
    public:
    	MyUserArea(void);
    	virtual ~MyUserArea();
    
    	virtual Bool Init(void);
    	virtual Bool GetMinSize(Int32& w, Int32& h);
    	virtual void Sized(Int32 w, Int32 h);
    	virtual Bool InitValues(void);
    	virtual void DrawMsg(Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer& msg);
    
    	// custom methods
    
    	void SetHoveredPolygon(BaseObject* object, const Int32& polygon);
    
    private:
    	Int32 mWidth;
    	Int32 mHeight;
    
    	AutoAlloc<GeClipMap> mClipmap;
    
    	BaseObject*	mObject;
    	Int32	mHoveredPolygon;
    };
    
    // ====================================
    // GeDialog
    // ====================================
    
    class MyDialog : public GeDialog
    {
    public:
    	MyDialog(void);
    	virtual ~MyDialog(void);
    	virtual Bool CreateLayout(void);
    	virtual void DestroyWindow(void);
    
    	MyUserArea	mUserArea;
    };
    
    // ====================================
    // CommandData
    // ====================================
    
    class MyCommand : public CommandData
    {
    	INSTANCEOF(MyCommand, CommandData)
    
    public:
    	virtual Bool Execute(BaseDocument* doc, GeDialog* parentManager);
    
    	MyDialog dlg;
    };
    

    UserArea.cpp

    // MyUserArea.cpp
    
    #include "c4d.h"
    #include "lib_clipmap.h"
    
    #include "MyUserArea.h"
    
    // ====================================
    // GeUserArea
    // ====================================
    
    
    MyUserArea* g_UserArea = nullptr;
    
    MyUserArea::MyUserArea(void) : mWidth(0), mHeight(0), mObject(nullptr), mHoveredPolygon(NOTOK) {}
    MyUserArea::~MyUserArea(void) {}
    
    Bool MyUserArea::Init(void)
    {
    	return true;
    }
    
    Bool MyUserArea::GetMinSize(Int32& w, Int32& h)
    {
    	w = 100;
    	h = 100;
    	return true;
    }
    
    void MyUserArea::Sized(Int32 w, Int32 h)
    {
    	mWidth = w;
    	mHeight = h;
    }
    
    Bool MyUserArea::InitValues(void)
    {
    	mWidth = GetWidth();
    	mHeight = GetHeight();
    	return true;
    }
    
    void MyUserArea::DrawMsg(Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer& msg)
    {
    	OffScreenOn();
    	const Int32 w = GetWidth();
    	const Int32 h = GetHeight();
    	const Int32 size = (w < h) ? w : h;
    	const Int32 halfsize = size / 2;
    
    	if (!mClipmap)
    		return;
    	mClipmap->Init(w, h, 32);
    	mClipmap->BeginDraw();
    
    	Vector color(64);
    	mClipmap->SetColor(SAFEINT32(color.x), SAFEINT32(color.y), SAFEINT32(color.z), 255);
    	mClipmap->FillRect(0, 0, w, h);
    
    	if (mObject && mObject->IsInstanceOf(Opolygon) && (mHoveredPolygon != NOTOK))
    	{
    		PolygonObject* polyObj = ToPoly(mObject);
    		const CPolygon* polyR = polyObj->GetPolygonR();
    		const Vector* pointR = polyObj->GetPointR();
    		const Int32 polygonCount = polyObj->GetPolygonCount();
    		const Int32 pointCount = polyObj->GetPointCount();
    
    		Float lenxinv, lenyinv, lenzinv;
    		Vector mapCenter, mapSize, vMin, vMax;
    
    		vMin.x = 100000.0;
    		vMin.y = 100000.0;
    		vMin.z = 100000.0;
    		vMax.x = -100000.0;
    		vMax.y = -100000.0;
    		vMax.z = -100000.0;
    
    		for (Int32 ndx = 0; ndx < pointCount; ndx++)
    		{
    			Vector pt = pointR[ndx];
    
    			if (pt.x < vMin.x) vMin.x = pt.x;
    			if (pt.x > vMax.x) vMax.x = pt.x;
    			if (pt.y < vMin.y) vMin.y = pt.y;
    			if (pt.y > vMax.y) vMax.y = pt.y;
    			if (pt.z < vMin.z) vMin.z = pt.z;
    			if (pt.z > vMax.z) vMax.z = pt.z;
    		}
    
    		mapSize.x = Abs(vMax.x - vMin.x);
    		mapSize.y = Abs(vMax.y - vMin.y);
    		mapSize.z = Abs(vMax.z - vMin.z);
    
    		mapCenter.x = vMin.x + (mapSize.x*0.5);
    		mapCenter.y = vMin.y + (mapSize.y*0.5);
    		mapCenter.z = vMin.z + (mapSize.z*0.5);
    
    		if (mapSize.x != 0.0) lenxinv = size / mapSize.x;    else lenxinv = 0.0;
    		if (mapSize.y != 0.0) lenyinv = size / mapSize.y;    else lenyinv = 0.0;
    		if (mapSize.z != 0.0) lenzinv = size / mapSize.z;    else lenzinv = 0.0;
    
    		for (Int32 i = 0; i < polygonCount; ++i)
    		{
    			Vector pt_a = (pointR[polyR[i].a] - mapCenter);
    			Vector pt_b = (pointR[polyR[i].b] - mapCenter);
    			Vector pt_c = (pointR[polyR[i].c] - mapCenter);
    			Vector pt_d = (pointR[polyR[i].d] - mapCenter);
    
    			const Int32 nvert = polyR[i].IsTriangle() ? 3 : 4;
    			iferr(GE_POINT2D* vPoints = NewMem(GE_POINT2D, nvert))
    				break;
    			
    			vPoints[0].x = SAFEINT32(pt_a.x*lenxinv) + halfsize;
    			vPoints[0].y = SAFEINT32(-pt_a.y*lenyinv) + halfsize;
    
    			vPoints[1].x = SAFEINT32(pt_b.x*lenxinv) + halfsize;
    			vPoints[1].y = SAFEINT32(-pt_b.y*lenyinv) + halfsize;
    
    			vPoints[2].x = SAFEINT32(pt_c.x*lenxinv) + halfsize;
    			vPoints[2].y = SAFEINT32(-pt_c.y*lenyinv) + halfsize;
    
    			if (nvert == 4)
    			{
    				vPoints[3].x = SAFEINT32(pt_d.x*lenxinv) + halfsize;
    				vPoints[3].y = SAFEINT32(-pt_d.y*lenyinv) + halfsize;
    			}
    
    			if (i == mHoveredPolygon)
    				mClipmap->SetColor(255, 255, 255, 255);
    			else
    				mClipmap->SetColor(128, 128, 128, 255);
    
    			mClipmap->FillPolygon(nvert, vPoints);
    			DeleteMem(vPoints);
    		}
    	}
    
    	mClipmap->EndDraw();
    	DrawBitmap(mClipmap->GetBitmap(), 0, 0, w, h, 0, 0, w, h, BMP_ALLOWALPHA);
    }
    
    void MyUserArea::SetHoveredPolygon(BaseObject* object, const Int32& polygon)
    {
    	mObject = object;
    	mHoveredPolygon = polygon;
    	Redraw();
    }
    
    
    // ====================================
    // GeDialog
    // ====================================
    
    MyDialog::MyDialog(void) {}
    MyDialog::~MyDialog(void) {}
    
    Bool MyDialog::CreateLayout(void)
    {
    	Bool res = GeDialog::CreateLayout();
    
    	GroupBegin(0, BFH_SCALEFIT | BFV_SCALEFIT, 1, 0, ""_s, 0);
    	GroupBorderSpace(4, 4, 4, 4);
    
    	// userarea for drawing
    	GroupBegin(0, BFH_SCALEFIT | BFV_SCALEFIT, 1, 0, String(), 0);
    	C4DGadget* ua = AddUserArea(IDC_USERAREA, BFH_SCALEFIT | BFV_SCALEFIT, 400, 400);
    	if (ua)
    		AttachUserArea(mUserArea, IDC_USERAREA);
    	GroupEnd();
    
    	GroupEnd();
    
    	return res;
    }
    
    void MyDialog::DestroyWindow(void)
    {
    	if (g_UserArea)
    		g_UserArea = nullptr;
    }
    
    // ====================================
    // CommandData
    // ====================================
    
    Bool MyCommand::Execute(BaseDocument* doc, GeDialog* parentManager)
    {
    	if (!dlg.IsOpen())
    	{
    		dlg.Open(DLG_TYPE::ASYNC, MYDIALOG_PLUGIN_ID, -1, -1, 0, 0, 0);
    		g_UserArea = &dlg.mUserArea;
    	}
    	else
    	{
    		if (g_UserArea)
    			g_UserArea->Redraw();
    	}
    
    	return true;
    }
    

    DescriptionTool.h

    // MyDescriptionTool.h
    
    #pragma once
    
    #include "c4d.h"
    
    // Dummy IDs - for demonstration purposes only
    #define MYDESCRIPTIONTOOL_PLUGIN_ID	1000002
    
    class MyDescriptionTool : public DescriptionToolData
    {
    public:
    	virtual Int32				GetToolPluginId() { return MYDESCRIPTIONTOOL_PLUGIN_ID; }
    	virtual const String		GetResourceSymbol() { return String("MyDescriptionTool_desc"_s); } // return the resource description filename (without extension)
    
    	virtual Bool		InitTool(BaseDocument* doc, BaseContainer& data, BaseThread* bt);
    	virtual void		FreeTool(BaseDocument* doc, BaseContainer& data);
    	virtual void		InitDefaultSettings(BaseDocument* doc, BaseContainer& data);
    	virtual Int32		GetState(BaseDocument* doc);
    	virtual Bool		GetCursorInfo(BaseDocument* doc, BaseContainer& data, BaseDraw* bd, Float x, Float y, BaseContainer& bc);
    	virtual TOOLDRAW	Draw(BaseDocument* doc, BaseContainer& data, BaseDraw* bd, BaseDrawHelp* bh, BaseThread* bt, TOOLDRAWFLAGS flags);
    
    private:
    	Float	mX, mY;
    	Int32	mPolygon;
    };
    

    DescriptionTool.cpp

    // MyDescriptionTool.cpp
    
    // ===== includes =====
    
    #include "c4d.h"
    #include "MyDescriptionTool.h"
    #include "MyUserArea.h"
    
    // resources
    //#include "MyDescriptionTool_desc.h"
    
    extern MyUserArea* g_UserArea;
    
    Bool MyDescriptionTool::InitTool(BaseDocument* doc, BaseContainer& data, BaseThread* bt)
    {
    	if (!DescriptionToolData::InitTool(doc, data, bt))
    		return false;
    
    	mX = mY = -1.0f;
    	mPolygon = NOTOK;
    
    	return true;
    }
    
    void MyDescriptionTool::FreeTool(BaseDocument* doc, BaseContainer& data)
    {
    	DescriptionToolData::FreeTool(doc, data);
    }
    
    void MyDescriptionTool::InitDefaultSettings(BaseDocument* doc, BaseContainer& data)
    {
    	DescriptionToolData::InitDefaultSettings(doc, data);
    }
    
    Int32 MyDescriptionTool::GetState(BaseDocument* doc)
    {
    	return CMD_ENABLED;
    }
    
    Bool MyDescriptionTool::GetCursorInfo(BaseDocument* doc, BaseContainer& data, BaseDraw* bd, Float x, Float y, BaseContainer& bc)
    {
    	BaseObject* object = doc->GetActiveObject();
    	if (!object)
    		return true;
    
    	mPolygon = NOTOK;
    	if (bc.GetId() == BFM_CURSORINFO_REMOVE)
    	{
    		// cursor leaves the viewport
    		mX = -1; mY = -1;
    		if (g_UserArea)
    			g_UserArea->SetHoveredPolygon(object, mPolygon);
    		DrawViews(DRAWFLAGS::ONLY_ACTIVE_VIEW | DRAWFLAGS::NO_THREAD | DRAWFLAGS::NO_ANIMATION, bd);
    		return true;
    	}
    
    	mX = x;
    	mY = y;
    
    	bc.SetString(RESULT_BUBBLEHELP, "MyDescriptionTool"_s);
    
    	// prepare the viewport select
    	Int32 l, t, r, b, w, h;
    	bd->GetFrame(&l, &t, &r, &b);
    	// in contrary to python, in C++ we need to add 1
    	// or ViewportSelect->Init will fail
    	w = r - l + 1;
    	h = b - t + 1;
    	if (x < 0 || x >= w || y < 0 || y >= h)
    	{
    		if (g_UserArea)
    			g_UserArea->SetHoveredPolygon(object, mPolygon);
    		DrawViews(DRAWFLAGS::ONLY_ACTIVE_VIEW | DRAWFLAGS::NO_THREAD | DRAWFLAGS::NO_ANIMATION, bd);
    		return true;
    	}
    	AutoAlloc<ViewportSelect> viewportSelect;
    	if (viewportSelect && viewportSelect->Init(w, h, bd, object, Medges, true, VIEWPORTSELECTFLAGS::NONE))
    	{
    		Int32 vpsX = SAFEINT32(x);
    		Int32 vpsY = SAFEINT32(y);
    		Int32 radius = 10;
    		const ViewportPixel* vpPixel = viewportSelect->GetNearestPolygon(object, vpsX, vpsY, radius);
    
    		mPolygon = vpPixel ? vpPixel->i : -1;
    
    		if (g_UserArea)
    			g_UserArea->SetHoveredPolygon(object, mPolygon);
    	}
    	DrawViews(DRAWFLAGS::ONLY_ACTIVE_VIEW | DRAWFLAGS::NO_THREAD | DRAWFLAGS::NO_ANIMATION, bd);
    	return true;
    }
    
    TOOLDRAW MyDescriptionTool::Draw(BaseDocument* doc, BaseContainer& data, BaseDraw* bd, BaseDrawHelp* bh, BaseThread* bt, TOOLDRAWFLAGS flags)
    {
    	if (mPolygon == NOTOK)
    		return TOOLDRAW::HANDLES | TOOLDRAW::AXIS;
    
    	if (flags == TOOLDRAWFLAGS::NONE)
    	{
    		BaseObject* object = doc->GetActiveObject();
    		if (object)
    		{
    			PolygonObject* polyObj = ToPoly(object);
    			Matrix mg = polyObj->GetMg();
    			const CPolygon* polyR = polyObj->GetPolygonR();
    			const Vector* pointR = polyObj->GetPointR();
    			if (polyR && pointR && (mPolygon < polyObj->GetPolygonCount()))
    			{
    				// prepare matrix for drawin in viewport, use z offset 6 to draw on top
    				Int32 lineZoffset = 6;
    				bd->SetMatrix_Matrix(polyObj, mg, lineZoffset);
    
    				const Vector selprevColor = GetViewColor(VIEWCOLOR_SELECTION_PREVIEW);
    
    				Vector p[4] = { pointR[polyR[mPolygon].a], pointR[polyR[mPolygon].b], pointR[polyR[mPolygon].c], pointR[polyR[mPolygon].d] };
    				Vector f[4] = { selprevColor, selprevColor, selprevColor, selprevColor };
    				bd->DrawPolygon(p, f, !polyR[mPolygon].IsTriangle());
    			}
    		}
    	}
    
    	return TOOLDRAW::HANDLES | TOOLDRAW::AXIS;
    }
    

    The description resource files are an empty .h and .res, as well as .str

    Here is a video capture with both Cinema4D versions next to each other to get an idea of the issue



  • Hi @C4DS I'm afraid you have hit another issue with GeUserArea in R21.

    I've reported the issue to our development team.
    And I will report you back when I have more information (I know I still have nothing on your previous topic, I don't forget you...)

    Thanks a lot for reporting this kind of issues, it's really relevant for us :)

    Cheers,
    Maxime.



  • @m_adam said in GeUserArea lag in R21 versus R20:

    Thanks a lot for reporting this kind of issues, it's really relevant for us :)

    My wife is "jokingly" telling me to send in an invoice for all the effort I am spending in "beta-testing" this official public release.
    Not so sure if she really means it as a joke, or just being sarcastic ...



  • Hi Dianiel, we finally spot the issue. The cause is a double redraw when GeUserArea::Redraw is called from a thread.
    To avoid such a double redraw you can do

    Redraw(GeIsMainThread() == false);
    

    Note this inverted behavior is introduced in r21 and most likely be fixed in next major release of Cinema 4D.

    Cheers,
    Maxime



  • @m_adam said in GeUserArea lag in R21 versus R20:

    Redraw(GeIsMainThread() == false);

    Sorry to say that with the changes applied to the code there is NO difference in behaviour.
    The lag is still present.



  • @C4DS said in GeUserArea lag in R21 versus R20:

    GetCursorInfo

    I noticed that in GetCursorInfo you are creating a ViewportSelect. This is being created, initialising and destroyed for every single mouse movement you are doing in your viewport. And can be very time consuming, especially as your object gets more complex. You should only call initialise once and then only do it again if the object changes.

    You should either create and cache this yourself. Or maybe use one of the static ViewportSelect::PickObject() methods that would be caching things for you.

    I do think ViewportSelect::PickObject() is what you are after here. That should help cut down some, if not all, of the lag you are seeing.



  • @kbar said in GeUserArea lag in R21 versus R20:

    @C4DS said in GeUserArea lag in R21 versus R20:

    GetCursorInfo

    You should either create and cache this yourself.

    Thanks for pointing this out, as mentioned the provided code is only to visualize the different behaviour between R20 and R21.
    In the original post I had provided a video, showing the same code running in R20 versus R21, clearly showing the lag with R21.
    I will try with an optimized implementation, but if behaviour would have been identical between R20 and R21 I would already have been happy with the not optimal implementation, and that is what I am trying to point out with this topic.



  • Yes your code could be optimized but we agree with you here this is not really the point, a R20 code is expected to have the same performance with R21 and here this is not the case.

    And @C4DS I agree using GeIsMainThread() == false I would say partially fix the issue (sometimes it works as R20, sometimes not) We keep investigating it.

    Cheers,
    Maxime.