C++ Panda3D Class, Render inside python threading,

I have created a C++ class and used it to render. The system is quite similar to Render inside python threads . However there is a difference that the C++ class is generated as a object in the main python function, then the render process is called inside threading.Thread().

when instead of threading.Thread() if I render normally, I get the results correctly, But when I render inside thread Panda3D gives following error :
:display:egldisplay(error): Failed to call eglMakeCurrent: EGL_BAD_ACCESS

Most probably the problem is about

If context is current to some other thread, or if either draw or read are bound to contexts in another thread, an EGL_BAD_ACCESS error is generated.

as explained here: eglMakeCurrent - EGL Reference Pages

Any suggestions?

here is c++ render class:

#include <stdint.h>
#include "pandaFramework.h"
#include "pandaSystem.h"
#include "load_prc_file.h"
#include "displayRegion.h" 
#include "genericAsyncTask.h" 
#include "asyncTaskManager.h" 
#include "eventHandler.h"    
#include "buttonThrower.h" 
#include "bulletWorld.h"
#include "bulletTriangleMesh.h" 
#include "bulletTriangleMeshShape.h"
#include "bulletRigidBodyNode.h"
#include "texture.h" 
#include "frameBufferProperties.h"
#include "graphicsOutput.h"
#include "graphicsEngine.h"
#include "graphicsPipe.h"
#include "graphicsPipeSelection.h"
#include "camera.h"
#include "windowProperties.h"
#include "transformState.h"
#include "nodePath.h"
#include "geomNode.h"
#include "pandaNode.h"
#include "perspectiveLens.h"
#include "loader.h"
#include "math.h"
#include "bulletClosestHitRayResult.h"

using namespace std;
class cpp_Render{
	public:
          PT(GraphicsOutput) mybuffer;
          PT(Texture) mytexture;
          PT(Camera)  mycamera;
          PT(DisplayRegion) region;
          NodePath mycameraNP;
          FrameBufferProperties fb_prop;
          PT(GraphicsPipe)  pipe;
          PT(GraphicsEngine) engine;
          WindowProperties win_prop;
          PerspectiveLens lens;
          NodePath altRender = NodePath("render");
          NodePath npm;
          PT(BulletWorld) world;
          PT(BulletTriangleMesh) mesh;
          NodePathCollection npc;
          PT(GeomNode) geomNode;
          PT(BulletTriangleMeshShape)  shape;
          PT(BulletRigidBodyNode) floor;
          PT(Loader) loader;
          NodePath floorNP;

		cpp_Render(char * model_path);
		void * cpp_render(float * camera_values);
};
cpp_Render::cpp_Render(char * model_path){
  // getting global pointers and setting environment
	load_prc_file_data("", "gl-debug true");
  //load_prc_file_data("", "gl-check-errors true");
  engine = GraphicsEngine::get_global_ptr();
  pipe = GraphicsPipeSelection::get_global_ptr()->make_module_pipe("pandagl");
  loader = Loader::get_global_ptr();
  
  // frame buffer properties
  fb_prop.set_rgb_color(true);
  fb_prop.set_rgba_bits(8, 8, 8, 8);
  fb_prop.set_depth_bits(8);

  win_prop.set_size(1920,1080);
  
  // camera properties
  mycamera = new Camera("new_camera");     
  mycameraNP =   NodePath(mycamera) ; 
  mycameraNP.reparent_to(altRender);
  mycamera->get_lens()->set_film_size( 1.0,0.5625  );  
  mycamera->get_lens()->set_focal_length(0.555555);
  mycameraNP.set_pos(-2.17,5.269,2.8359);
  mycameraNP.set_hpr(172.5,-23.99,0.00);

  // creating buffer
  int flags = pipe->BF_refuse_window;
  mybuffer = engine->make_output(pipe, "buffer", -100, fb_prop, win_prop, flags);
  mybuffer->set_clear_color_active(true);
  mybuffer->set_clear_depth_active(true);
  mybuffer->set_clear_depth(1.0);
  mybuffer->set_clear_color(LColor(0,0,0,0));
  mybuffer->add_render_texture(mytexture, GraphicsOutput::RTM_copy_ram);

  // loading model
  npm = NodePath(loader->load_sync(model_path));
  npm.flatten_light();
  npm.reparent_to(altRender);
  region = mybuffer->make_display_region();
  region->set_camera(mycameraNP);

  // model -> world
  world = new BulletWorld();
  mesh = new BulletTriangleMesh();
  npc = npm.find_all_matches("**/+GeomNode");
  
  for (size_t i = 0; i < npc.get_num_paths(); i++) {
    geomNode = DCAST(GeomNode, npc.get_path(i).node());
    for (size_t j = 0; j<geomNode->get_num_geoms();j++){
      mesh->add_geom(geomNode->get_geom(j),npc.get_path(i).get_transform());
    }
  }

  shape = new BulletTriangleMeshShape(mesh,false);
  floor = new BulletRigidBodyNode("floor");
  floor->add_shape(shape);
  floorNP = altRender.attach_new_node(floor);
  floorNP.set_pos(0,0,0);
  floorNP.set_collide_mask(BitMask32::all_on());
  world->attach_rigid_body(floor);
  mycamera->get_lens()->set_focal_length(0.555555);
  mycameraNP.set_pos(-2.17,5.269,2.8359);
  mycameraNP.set_hpr(172.5,-23.99,0.00);

  // initiliaze the system by getting some renders
  for (int i = 0; i<10; i++){
    mybuffer->get_engine()->render_frame();
    mytexture = mybuffer->get_texture();
  }
};

void * cpp_Render::cpp_render(float * camera_values){

  mycamera->get_lens()->set_focal_length(*camera_values);
  mycameraNP.set_pos(*(camera_values+1),*(camera_values+2),*(camera_values+3));
  mycameraNP.set_hpr(*(camera_values+4),*(camera_values+5),*(camera_values+6));
  mybuffer->get_engine()->render_frame();
  mytexture = mybuffer->get_texture();
  CPTA_uchar data = mytexture->get_ram_image();
  void *_ptr = (void*)data.p();
  return _ptr;
};

extern "C" {

  cpp_Render* Render(char * model_path){ return new cpp_Render(model_path); }
	void *  RenderRender(cpp_Render* Render, float* camera_values ){ return Render -> cpp_render(camera_values); }
  }
}

here is Python class that uses c++ class

from ctypes import cdll
import time
import datetime
import numpy as np
import matplotlib.pyplot as plt
import ctypes
#import threading
from direct.stdpy import threading2 as threading
import cv2
import os

class RenderCpp:
    def __init__(self):
        render_model_path = "./model.egg"
      
        self.renderlib = ctypes.cdll.LoadLibrary('./librender.so')
        self.renderlib.RenderRender.restype = ctypes.c_void_p
        self.renderlib.RenderRender.argtypes = [ctypes.c_void_p,ctypes.c_void_p]
        self.renderlib.Render.argtypes = [ctypes.c_char_p]
        self.renderlib.Render.restype = ctypes.c_void_p
        encoded_model_path = render_model_path.encode('utf-8')
        self.render = self.renderlib.Render(encoded_model_path)

    def render_image(self,i):
        camera_values = np.array([0.5555,-2.17,5.269,2.8359,172.5,-23.99,5*i],np.float32)
        elma = self.renderlib.RenderRender(self.render,camera_values.ctypes)
        buffer_as_ctypes_array = ctypes.cast(elma, ctypes.POINTER(ctypes.c_void_p*(int(1920*1080*4/8))))[0]
        image_anim = np.frombuffer(buffer_as_ctypes_array, np.uint8).reshape(1080,1920,4)
        image_anim = np.flipud(image_anim)
        cv2.imwrite("render_res"+str(i)+".png",image_anim)
        return image_anim

# the threading and parallelization code:
def process_render_cpp():
    is_parallel = True
    # getting the class
    rend = RenderCpp()
    if is_parallel:
        processes = []
        tic = time.time()
        for i in range (10):
            processes.append(threading.Thread(target=rend.render_image,args=(i,)))
        for i in range(len(processes)):
            processes[i].start()
            time.sleep(0.1)
        
        for i in range(len(processes)):
            processes[i].join()

        toc = time.time()
    else:
        for i in range(10):
            rend.render_image(i)

if __name__ == "__main__":
    process_render_cpp()

Yeah, your guess is probably correct.

You might have to either:

  • Make sure that Panda’s window opening / render functions are always called from the same thread.
  • Modify the Panda source, specifically eglGraphicsWindow.cxx, calling this at the end of end_frame:
eglMakeCurrent(_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)

@rdb Thank you for suggestion, I found a solution, if any problems occur, I will try your suggestion.

for now I have included the
load_prc_file_data("",“threading-model cull/draw”);
to the cpp_Render class construction.

I suppose getting render and filling the RAM buffer done in a different thread, thus I have to take the image after a small amount of time. for that reason I have changed the python part as

camera_values = np.array([0.5555,-2.17,5.269,2.8359,172.5,-23.99,5*i],np.float32)
        elma = self.renderlib.RenderRender(self.render,camera_values.ctypes)
        time.sleep(0.001)
        buffer_as_ctypes_array = ctypes.cast(elma, ctypes.POINTER(ctypes.c_void_p*(int(1920*1080*4/8))))[0]
        image_anim = np.frombuffer(buffer_as_ctypes_array, np.uint8).reshape(1080,1920,4)

Now I get the results correctly.

Note: when using thread I also have to use mutex for the program not to access to the camera and render at the same time.