Render inside python threads

I have a python application. I need to render with panda3d inside a thread in this application. I do the rendering on the cpp side. I transfer the render data to the python side using ctypes. However, I am facing a problem. On the Python side, if I do these operations without opening a thread, it works smoothly. I get the render image I want, but when I run the same function in the thread, I get a segmentation error. How can I solve this problem?

I am rendering on a headless server. I built panda3d without x11 support with
“python3 makepanda/makepanda.py --everything --installer --threads 16 --no-x11”.
“sudo dpkg -i panda3d*.deb”

My python code:

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



def process_render():
    camera_values = np.array([0.5555,-114.92007343418689, -8.148216295002753, 16.654022979044203, -179.98575181566176, -55.97798890611995, 0.006786534125852574],np.float32)
    
    model_path = "model_path.egg"
    encoded_model_path = model_path.encode('utf-8')
    
    liblr = cdll.LoadLibrary('so_file_path/librender.so')
    liblr.RenderFunc.restype = ctypes.c_void_p
    liblr.Render.argtypes = [ctypes.c_void_p]
    render = liblr.Render(encoded_model_path)

    render_result = liblr.RenderFunc(render, camera_values.ctypes)
    buffer_as_ctypes_array = ctypes.cast(render_result, ctypes.POINTER(ctypes.c_void_p*(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_cpp.png",image_anim)  ## I process the image later on. I don't need to save image to disk. debug purposes only.


if __name__ == "__main__":
    
    process_render()  # this works completely fine. 

    # t = threading.Thread(target=process_render, args=())  ## if i run this. I get segmentation fault.
    # t.start()

My cpp code:

#include "displayRegion.h"
#include <chrono>
#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 "nodePath.h"
#include "pandaNode.h"
#include "loader.h"


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;
    NodePath altRender = NodePath("render");
    NodePath model;
	  PT(Loader) loader;

		cpp_Render(char* model_path);
		void* render_function(float* camera_values);
    
};

cpp_Render::cpp_Render(char * model_path){

  engine = GraphicsEngine::get_global_ptr();
  pipe = GraphicsPipeSelection::get_global_ptr()->make_module_pipe("pandagl");
  loader = Loader::get_global_ptr();

  fb_prop.set_rgb_color(true);
  fb_prop.set_rgba_bits(8, 8, 8, 8);
  
  win_prop.set_size(1920,1080);
  mytexture = new Texture("texture");
  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.5);
  mycameraNP.set_pos(0, 0, 0);
  mycameraNP.set_hpr(0, 0, 0);

  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);

  model = NodePath(loader->load_sync(model_path));
  model.flatten_light();
  model.reparent_to(altRender);

  region = mybuffer->make_display_region();
  region->set_camera(mycameraNP);

  mybuffer->get_engine()->render_frame();
  mybuffer->get_engine()->render_frame();  
};

void* cpp_Render::render_function(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* data_ptr = (void*)data.p();
  return data_ptr;
};

extern "C" {

  cpp_Render* Render(char * model_path){ 
    return new cpp_Render(model_path); 
    }

	void* RenderFunc(cpp_Render* Render, float* camera_values ){ 
    return Render -> render_function(camera_values); 
    }

}

Cpp Compile Flags

g++ -std=c++11 -c -w -Wall -fpic render.cpp -I/home/ubuntu/cpp_panda/panda3d/built/include -I/usr/include/eigen3 -I/usr/include/bullet

g++ -std=c++11 -shared render.o -o librender.so -L/usr/lib/x86_64-linux-gnu -L/home/ubuntu/cpp_panda/panda3d/built/lib -lp3direct -lp3dtool -lp3dtoolconfig -lp3framework -lp3interrogatedb -lp3vision -lpanda -lpandaai -lpandabullet -lpandaegg -lpandaexpress -lpandafx -lpandafx -lpandaode -lpandaphysics -lpandaskel -lBullet3Common -lBulletDynamics -lBullet3Dynamics -lBullet3Geometry -lpthread -lLinearMath -Wl,–no-undefined

You’ve forgotten to set all of the argument and return types, so your pointer values are getting truncated to 32-bit integers:

    liblr.RenderFunc.restype = ctypes.c_void_p
    liblr.RenderFunc.argtypes = [ctypes.c_void_p]
    liblr.Render.restype = ctypes.c_void_p
    liblr.Render.argtypes = [ctypes.c_void_p]

Without this change, it only works if the pointer values just so happen to have all-zeroes in the higher 32 bits.

1 Like

Thank you a lot, this solved my problem. But now I have more questions.

While rendering inside a loop, I get this

“:display:gsg:glgsg(error): GL error 0x502 : invalid operation
:display:gsg:glgsg(error): An OpenGL error has occurred. Set gl-debug #t in your PRC file to display more information.”

errors time to time but render results seems as expected. After a certain period of time I get this

“:display(error): Deactivating eglGraphicsStateGuardian.”

error. After this error i can’t get render anymore.
Interestingly when i added “load_prc_file_data(”", “gl-debug true”);" to my cpp file, I get this

“:display:gsg:glgsg(error): GL_INVALID_OPERATION error generated.
:display:gsg:glgsg(error): GL_INVALID_OPERATION error generated. The required buffer is missing.”

errors. But there is no “Deactivating eglGraphicsStateGuardian” error anymore. Rendering process works as expected for all loop time. So what causes this strange behaviour? How to prevent “Deactivating eglGraphicsStateGuardian” error.