Search Unity

Question How can I force render?

Discussion in 'Web' started by Marks4, Oct 24, 2021.

  1. Marks4

    Marks4

    Joined:
    Feb 25, 2018
    Posts:
    547
    See the accepted answer. How could I call renderer.render( scene, camera ); in the javascript side to force rendering to the drawing buffer?
     
  2. Marks4

    Marks4

    Joined:
    Feb 25, 2018
    Posts:
    547
    Basically, any way to call this function from the javascript side?
     
  3. Marks4

    Marks4

    Joined:
    Feb 25, 2018
    Posts:
    547
  4. KamilCSPS

    KamilCSPS

    Joined:
    May 21, 2020
    Posts:
    448
    Jukka has been on leave from Unity since early summer. Better tag someone active in Unity.
     
  5. Marks4

    Marks4

    Joined:
    Feb 25, 2018
    Posts:
    547
    @brendanduncan_u3d Can you help? Please, I need to know how to force a draw call in js, similar to how it's done with threejs. What would be the equivalent for Unity WebGL on the javascript side?
     
    Last edited: Oct 28, 2021
  6. brendanduncan_u3d

    brendanduncan_u3d

    Unity Technologies

    Joined:
    Jul 30, 2019
    Posts:
    437
  7. Marks4

    Marks4

    Joined:
    Feb 25, 2018
    Posts:
    547
    @brendanduncan_u3d Sorry if I wasn't clear. I want to be able to force a draw call on a jslib or jspre file. Please see this link. It's the first solution. I don't know how to do it. What is the command I can use to have the effect of drawing to the buffer?
     
  8. Marks4

    Marks4

    Joined:
    Feb 25, 2018
    Posts:
    547
    @brendanduncan_u3d Perphaps one of th webgl devs knows how to do it? I'm sure it's possible. I just need to know in the webgl.framework.js, what function or whatever is used to draw to the buffer?
     
  9. brendanduncan_u3d

    brendanduncan_u3d

    Unity Technologies

    Joined:
    Jul 30, 2019
    Posts:
    437
    Ok, so my take on what you're asking for is to be able to grab render from the canvas during a time when Unity isn't in the middle of a render so the canvas hasn't been cleared, and you do not want to include any C# code in the project itself, you want to do it entirely from JS.

    In that case, you could just wait for the beginning of the canvas's refresh cycle using JS's requestAnimationFrame. If the project is using default framerate, it would also be using requestAnimationFrame to trigger the frame updates and render.

    Something like:
    Code (JavaScript):
    1. canvas.addEventListener("click", function() {
    2.         const saveBlob = (function() {
    3.             const a = document.createElement('a');
    4.             document.body.appendChild(a);
    5.             a.style.display = 'none';
    6.             return function saveData(blob, fileName) {
    7.                 const url = window.URL.createObjectURL(blob);
    8.                 a.href = url;
    9.                 a.download = fileName;
    10.                 a.click();
    11.             };
    12.         }());
    13.         requestAnimationFrame(function() {
    14.             canvas.toBlob((blob) => {
    15.                 saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
    16.             });
    17.         })
    18. });
     
  10. Marks4

    Marks4

    Joined:
    Feb 25, 2018
    Posts:
    547
    @brendanduncan_u3d Thanks, but I tried requestAnimationFrame, and it doesn't work 100% of the time(although it works better than without, but it needs to be 100%). I really need to force a canvas draw like the examples I showed.
     
  11. brendanduncan_u3d

    brendanduncan_u3d

    Unity Technologies

    Joined:
    Jul 30, 2019
    Posts:
    437
    There's really not anything in framework.js you could use from pure JS, there's no "present buffer" command and there's no force draw call.

    You could go real hacky and start hijacking the canvas functions. The WebGL context is gotten from `context.getContext("webgl2")`. One function that happens at the very beginning of a frame is context.isContextLost. So you could hijack context.isContextLost, it will likely be at the beginning of the frame prior to it having been cleared by Unity. However, preserveDrawingBuffer is false so the browser will have cleared the canvas, so you need to hijack canvas.getContext too, and force the preserveDrawingBuffer to be true. Of course, if you start poking around at this level, you'll be breaking your warranty :)

    Code (JavaScript):
    1. var saveBlob = (function() {
    2.     var a = document.createElement('a');
    3.     document.body.appendChild(a);
    4.     a.style.display = 'none';
    5.     return function saveData(blob, fileName) {
    6.         const url = window.URL.createObjectURL(blob);
    7.         a.href = url;
    8.         a.download = fileName;
    9.         a.click();
    10.     };
    11. }());
    12.  
    13. // Hijack canvas getContext to force preserveDrawingBuffer to be true so the
    14. // browser doesn't clear.
    15. var origGetContext = canvas.getContext;
    16. canvas.getContext = function(name, options) {
    17.     options = options || {};
    18.     options.preserveDrawingBuffer = true;
    19.     return origGetContext.call(canvas, name, options);
    20. };
    21.  
    22. // Hijack isContextLost since it will likely be the first command in the Unity rendering process.
    23. var ctx = canvas.getContext("webgl2");
    24. // Use doScreenShot as a flag to indicate we want to do a canvas grab
    25. var doScreenShot = false;
    26. var origIsContextLost = ctx.isContextLost;
    27. ctx.isContextLost = function() {
    28.     if (doScreenShot) {
    29.         doScreenShot = false;
    30.         canvas.toBlob((blob) => {
    31.             saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
    32.         });
    33.     }
    34.     origIsContextLost.call(ctx, ...arguments);
    35. };
    36.  
    37. canvas.addEventListener("click", function() {
    38.     doScreenShot = true;
    39. });
     
  12. Marks4

    Marks4

    Joined:
    Feb 25, 2018
    Posts:
    547
    @brendanduncan_u3d I see, thank you. That's very helpful. You are right, this seems too much. It would probably break something. But I will try. I have 2 questions

    1. How can I restore everything after the screenshot?
    2. Is it canvas.getContext("webgl2"); even if the browser is under webgl1? How do I get the correct context if it's webgl1 and not 2? How can I know?
     
  13. brendanduncan_u3d

    brendanduncan_u3d

    Unity Technologies

    Joined:
    Jul 30, 2019
    Posts:
    437
    1. You can just put the origIsContextLost and origGetContext back.
    2. You can put the hijacking of isContextLost into getContext, to make it work for whatever context is requested.

    Here's an updated (though now I haven't tested it):
    Code (JavaScript):
    1. var saveBlob = (function() {
    2.     var a = document.createElement('a');
    3.     document.body.appendChild(a);
    4.     a.style.display = 'none';
    5.     return function saveData(blob, fileName) {
    6.         const url = window.URL.createObjectURL(blob);
    7.         a.href = url;
    8.         a.download = fileName;
    9.         a.click();
    10.     };
    11. }());
    12.  
    13. // Use doScreenShot as a flag to indicate we want to do a canvas grab
    14. var doScreenShot = false;
    15. var origIsContextLost = null;
    16. // Hijack canvas getContext to force preserveDrawingBuffer to be true so the
    17. // browser doesn't clear.
    18. var origGetContext = canvas.getContext;
    19.  
    20. canvas.getContext = function(name, options) {
    21.     // If it's a webgl1 or webgl2 context...
    22.     if (!origIsContextLost && name.includes("webgl")) {
    23.         options = options || {};
    24.         options.preserveDrawingBuffer = true;
    25.         var ctx = origGetContext.call(canvas, name, options);;
    26.  
    27.         // Hijack isContextLost since it will likely be the first command in the Unity rendering process.
    28.         origIsContextLost = ctx.isContextLost;
    29.         ctx.isContextLost = function() {
    30.             if (doScreenShot) {
    31.                 doScreenShot = false;
    32.                 canvas.toBlob((blob) => {
    33.                     saveBlob(blob, `screencapture-${canvas.width}x${canvas.height}.png`);
    34.                 });
    35.  
    36.                 // Restore everything to it's original state
    37.                 ctx.isContextLost = origIsContextLost;
    38.                 canvas.getContext = origGetContext;
    39.             }
    40.             return origIsContextLost.call(ctx, ...arguments);
    41.         };
    42.         return ctx;
    43.     }
    44.     // Not a webgl context, or isContextLost has already been wrapped
    45.     return origGetContext.call(canvas, name, options);
    46. };
    47. canvas.addEventListener("click", function() {
    48.     doScreenShot = true;
    49. });
    50.  
     
    Last edited: Oct 29, 2021
    Marks4 likes this.
  14. Marks4

    Marks4

    Joined:
    Feb 25, 2018
    Posts:
    547
    @brendanduncan_u3d How about this method? This wait for end of frame code in C#, there must be something going on in the javascript side right? How can the C# know to wait for the end of frame? Maybe the answer is on the javascript bindings for that function!
     
  15. brendanduncan_u3d

    brendanduncan_u3d

    Unity Technologies

    Joined:
    Jul 30, 2019
    Posts:
    437
    No, sorry, there isn't anything on the javascript side. Besides requestAnimationFrame, there is nothing in the JS side that is frame specific, it all happens in the C++ engine, which C# is a layer on top of.
     
    Marks4 likes this.