Issues Rendering Textures in WebGL

Hello!

I've been attempting to render a texture via WebGL using the latest OCCT release (7.4.1 beta), but I can't quite get it to work. Ultimately the end result is a black surface and a browser warning: "RENDER WARNING: there is no texture bound to the unit 0".

I'm compiling via Emscripten, and have already compiled the opencascade library with the third party FreeType and FreeImage libraries.

This occurs in a pretty minimal arrangement using the existing WebGL sample. I've tweaked the file upload bit to load an image file into a pixmap to be used as a texture. Although I've tried several different approaches, the "simplest" approach of rendering an AIS_SHAPE with a texture configuration as described in its header file produces the previously mentioned result. My code for this follows
(This is in the onFileDataRead function in the WebGL sample, everything else is mostly the same, in some cases there were some issues I had to resolve in the default WasmOcctView.cpp file)
 

  const char* aName = theOpaque != NULL ? (const char* )theOpaque : "";

  Standard_ArrayStreamBuffer aStreamBuffer ((const char* )theBuffer, theDataLen);
  std::istream aStream (&aStreamBuffer);
  
  Message::DefaultMessenger()->Send (OSD_MemInfo::PrintInfo(), Message_Trace);

  Handle(Image_AlienPixMap) pixmap = new Image_AlienPixMap();
  pixmap->Load((Standard_Byte*)theBuffer, theDataLen, aName);
    
  double w = (double)(pixmap->Width()); 
  double h = (double)(pixmap->Height());

  Handle(AIS_Shape) aPrs = new AIS_Shape(BRepBuilderAPI_MakeFace(gp_Pln(), -w, 0, -h, 0));
  aPrs->Attributes()->SetupOwnShadingAspect();
  aPrs->Attributes()->ShadingAspect()->Aspect()->SetTextureMapOn();
  aPrs->Attributes()->ShadingAspect()->Aspect()->SetTextureMap (new Graphic3d_Texture2Dmanual (pixmap));
  aViewer.Context()->Display(aPrs, 1, -1, true);
  aViewer.View()->Redraw();
  aViewer.Context()->UpdateCurrentViewer();

Since I was wrestling with this for a bit I also tried an alternate approach to try have a little more control of the details of what was being set by "manually" creating an array of primitives to display the texture as follows
 

  const char* aName = theOpaque != NULL ? (const char* )theOpaque : "";

  Standard_ArrayStreamBuffer aStreamBuffer ((const char* )theBuffer, theDataLen);
  std::istream aStream (&aStreamBuffer);
  
  Message::DefaultMessenger()->Send (OSD_MemInfo::PrintInfo(), Message_Trace);
  Handle(Image_AlienPixMap) pixmap = new Image_AlienPixMap();
  pixmap->Load((Standard_Byte*)theBuffer, theDataLen, aName);

  Handle(Graphic3d_AspectFillArea3d) aspect = new Graphic3d_AspectFillArea3d();
  Handle(Graphic3d_Texture2Dmanual) texture_map = new Graphic3d_Texture2Dmanual(pixmap);
  // Due to limited non-power-of-two sized texture support in WebGL, disable related items
  // as described https://www.khronos.org/webgl/wiki/WebGL_and_OpenGL_Differences#Non-Power_of_Two_Texture_Support
  // That said, it doesn't work when using a power-of-two sized texture either so this is more in theory
  texture_map->SetMipMaps(false);
  texture_map->DisableRepeat();
  texture_map->DisableSmooth();
  Handle(Graphic3d_TextureSet) texture_set = new Graphic3d_TextureSet(texture_map);
  aspect->SetTextureSet(texture_set);
  aspect->SetTextureMapOn();
  aspect->SetInteriorStyle(Aspect_IS_SOLID);
  aspect->SetShadingModel(Graphic3d_TOSM_UNLIT);
  
  Handle(Graphic3d_ArrayOfTriangleStrips) aTriangles = new Graphic3d_ArrayOfTriangleStrips (4, 0, false, false, false, true);
  double w = (double)(pixmap->Width()); 
  double h = (double)(pixmap->Height());
  aTriangles->AddVertex (gp_Pnt(-w/2., -h/2., 0.0), gp_Pnt2d(0, 0));
  aTriangles->AddVertex (gp_Pnt(-w/2.,  h/2., 0.0), gp_Pnt2d(0, 1));
  aTriangles->AddVertex (gp_Pnt(w/2., -h/2., 0.0), gp_Pnt2d(1, 0));
  aTriangles->AddVertex (gp_Pnt(w/2.,  h/2., 0.0), gp_Pnt2d(1, 1));

  Handle(Graphic3d_Structure) img_struct = new Graphic3d_Structure(aViewer.View()->Viewer()->StructureManager());
  img_struct->SetVisual(Graphic3d_TOS_ALL);
  img_struct->Display();
  Handle(Graphic3d_Group) graphic_group = img_struct->CurrentGroup();

  graphic_group->SetPrimitivesAspect(aspect);
  graphic_group->AddPrimitiveArray(aTriangles);
  // graphic_group->SetGroupPrimitivesAspect (aspect);
  // graphic_group->SynchronizeAspects();

  aViewer.View()->Redraw();
  aViewer.Context()->UpdateCurrentViewer();

but again, no luck, same result.

Going by the warning it seems like it's related to the texture not getting bound, but going through the source so far there it appears that everything necessary for texture binding is appropriately configured. So I'm at a bit of a loss. I tried these arrangements (and many more, with different tweaks to the texture configurations, using different texture map classes like Graphic3d_2dplane, etc.) with a few different newer commit versions of OCCT just in case but nothing seems to resolve the issue.

I also checked the pixel values of the pixmap just in case, and they looked valid (along with the dimensions of the image used in the code snippets), so it would appear that FreeImage is working normally.

Is there something I'm missing here? How can I render image textures when compiling to use WebGL?

Thanks for any help or suggestions!

Kirill Gavrilov's picture

I have not tried FreeImage (is it difficult to build using Emscripten?) but I don't see any warnings "RENDER WARNING: there is no texture bound to the unit 0" in log within a small test in OCCT sample. What is your browser and configuration?

#include <Graphic3d_Texture2Dmanual.hxx>
static Handle(Image_PixMap) myDefaultTexture;
extern "C" void onFileDataRead (void* theOpaque, void* theBuffer, int theDataLen) {
  const TCollection_AsciiString aName (theOpaque != NULL ? (const char* )theOpaque : "");
  Standard_ArrayStreamBuffer aStreamBuffer ((const char* )theBuffer, theDataLen);
  std::istream aStream (&aStreamBuffer);
  TopoDS_Shape aShape; BRep_Builder aBuilder; BRepTools::Read (aShape, aStream, aBuilder);

  Handle(AIS_Shape) aShapePrs = new AIS_Shape (aShape);
  aShapePrs->SetMaterial (Graphic3d_NOM_SILVER);
  if (!myDefaultTexture.IsNull()) {
    const Handle(Graphic3d_AspectFillArea3d)& anAspects = aShapePrs->Attributes()->ShadingAspect()->Aspect();
    anAspects->SetTextureMap (new Graphic3d_Texture2Dmanual (myDefaultTexture));
    anAspects->SetTextureMapOn (true);
  }

  aViewer.Context()->Display (aShapePrs, AIS_Shaded, 0, false);
  aViewer.View()->FitAll (0.01, false);
  aViewer.View()->Redraw();
  Message::DefaultMessenger()->Send (TCollection_AsciiString("Loaded file ") + aName, Message_Info);
}

//! File read error event.
static void onFileReadFailed (void* theOpaque) {
  const char* aName = (const char* )theOpaque;
  Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: unable to load file ") + aName, Message_Fail);
}

//! Image file read event.
extern "C" void onImageRead (const char* theFilePath) {
  int aSizeX = 0, aSizeY = 0;
  char* anImgData = emscripten_get_preloaded_image_data (theFilePath, &aSizeX, &aSizeY);
  if (anImgData == nullptr) {
    Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: invalid image ") + theFilePath, Message_Fail);
  }

  Message::DefaultMessenger()->Send (TCollection_AsciiString("Loaded image ") + theFilePath + "@" + aSizeX + "x" + aSizeY, Message_Info);
  /// TODO memleak: free(anImgData) should be added somehow, by subclassing Image_PixMap etc.
  Handle(Image_PixMap) anImage = new Image_PixMap();
  anImage->InitWrapper (Image_Format_RGBA, (Standard_Byte* )anImgData, aSizeX, aSizeY);
  myDefaultTexture = anImage;
}

extern "C" void onImageReadFailed (const char* theFilePath) {
  Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: unable to load image ") + theFilePath, Message_Fail);
}

int main() {
  Message::DefaultMessenger()->Printers().First()->SetTraceLevel (Message_Trace);
  Handle(Message_PrinterSystemLog) aJSConsolePrinter = new Message_PrinterSystemLog ("webgl-sample", Message_Trace);
  Message::DefaultMessenger()->AddPrinter (aJSConsolePrinter); // open JavaScript console within the Browser to see this output
  Message::DefaultMessenger()->Send (TCollection_AsciiString("NbLogicalProcessors: ") + OSD_Parallel::NbLogicalProcessors(), Message_Trace);
  emscripten_set_main_loop (onMainLoop, -1, 0);
  aViewer.run();

  emscripten_async_wget_data ("samples/Ball.brep", (void* )"samples/Ball.brep", onFileDataRead, onFileReadFailed);
  emscripten_async_wget("OCC_logo.png", "/emulated/OCC_logo.png", onImageRead, onImageReadFailed);
  return 0;
}

 

p, li { white-space: pre-wrap; }

Kirill Gavrilov's picture

Which browsers have you checked? What is your configuration? 

Alex Ramsay's picture

Hey Kirill,

Using Firefox 78.0.2 and Chrome 83.0.4103.97 on Ubuntu 20, but your sample worked for me just fine so it must have been something with the FreeImage decoding. It took me a bit to figure out what classes to use in the first place so once I found Image_AlienPixMap and realized it required an image lib I got to work getting FreeImage compiled in without realizing there was an easier way! I hadn't considered getting the decoded image from the browser, and haven't use emscripten's image preload plugins before, but it's definitely a preferable approach here.

As for FreeImage, not really sure what the issue there is. I had to tweak some things (adding a few includes, adding a couple return value types that were void, and adding a couple define macros to use the right byte swapping function) to get it to compile, but it seemed to otherwise go through fine. Although they seem minor I can't rule out the tweaks didn't cause some issue, or if there's deeper incompatiblity. That said, as I originally mentioned it did return valid pixel values when querying the decoded pixmap directly, so it still seems like it was at least partly functional.

In any case, I've rewritten things to decode the image with the browser in js when it gets selected by the file input, and it works just fine. I greatly appreciate the assistance!

As a side thing, not sure if I should just create a new thread, but there's a rendering artifact I'm not quite sure how to eliminate that's unrelated to textures. Seems like it would be something simple but: all objects in the scene having a 2d light grey border around them (not the selection highlighting). One of the builds I tried when I was trying to fix the texture issue didn't have it, although I don't know what was different there. Any ideas for relating to that?

Thanks for your time.

Kirill Gavrilov's picture

all objects in the scene having a 2d light grey border around them (not the selection highlighting) 

Could you share some screenshots with this effect?
And it would be good registering an issue on a Bugtracker with necessary information (usage scenario / reproducing steps / browser version / graphics driver version, etc.).

Kirill Gavrilov's picture

As for FreeImage, not really sure what the issue there is.

Using image decoding routines provided by Emscripten itself has nice features - they rely on image decoders built-in into browser itself (most likely - native), has minimal impact on WebAssembly size
automatically handles asynchronous decoding (most likely in non-main JavaScript working thread), etc.

From the other side, this approach restricts image file format that could be opened, as well as their pixel formats (basically to 32-bit RGBA) - thus, using FreeImage or another native image library may be useful.
From performance point of view, it should be good, but might have some extra overhead - so that it would be interesting to compare.

FreeImage is not very active project - it still uses SVN repository, not very friendly environment for contributions or for sharing intermediate changes (like making a fork on github).

Still, you may try sharing your changes or contributing to FreeImage project or just sharing a build with headers so that other may try reproducing the problem and see what might be wrong.

Alex Ramsay's picture

I've attached a few screenshots illustrating the effect, which occurs on both browsers.
I'll see if I can add a ticket to the bug tracker if it appears to be a bug and not user error on my part.

And for FreeImage, yeah I got that impression taking a look at it. However I think I'll just be removing it from my OCCT build and use the existing image handling tools, since I only included it under the misconception I needed it in the first place. Perhaps I'll revisit it if I encounter other needs for the deeper image processing it seemed to provide.
 

Attachments: 
Kirill Gavrilov's picture

Using Firefox 78.0.2 and Chrome 83.0.4103.97 on Ubuntu 20

Have you tried the same sample on other systems like Windows?
To check if it is specific to Linux or to something else...

I recall that I've seen such kind of actifacts somewhere (occurred suddenly), but don't recall where...

Anton Shabalin's picture

Hello!

I have same issues on Linux. Does anyone have a solution ?