Skip to main content

News

Topic: Sphere v2 API discussion (Read 34865 times) previous topic - next topic

0 Members and 1 Guest are viewing this topic.
  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Sphere v2 API discussion
Reply #60
For transform.project3D() (to create a perspective projection), should the function work like glFrustum() or gluPerspective()?  The former lets you put the viewport in any orientation you want but is less intuitive to work with; while the latter accepts more human-readable parameters: field of view angle and aspect ratio, but forces you to use an on-axis projection (i.e. the camera points at 0,0 in clipspace always)
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Rhuan
  • [*][*][*][*]
Re: Sphere v2 API discussion
Reply #61
1. In answer to your question: I'd go with glFrustrum, can always add a helper function to simplify if needed.

2. I was trying to use logger.js earlier but couldn't get it to work:
const logger = require("logger");

var log = new logger.Logger("some file");
-> cannot construct undefined

3. I tried copy and pasting the code into my script then ran into problems with read/write access - the "user safe file directory" was not defined so I couldn't use ~/ and the game's folder isn't writeable so I couldn't use @/ or just a path without a prefix.

4. I've being doing some tests with galileo, now some of what it can do is great BUT, I tried having it run brownian motion and it seems slower than using transformBlit. I'm talking about drawing 400-1000 rings on screen, each one spinning (deformation of shape), moving in x-y, (translation) and moving in z (scaling).

With Galileo I tried doing these as:
(results based on modelling brownian motion with 800 rings on screen)
a) an array of models each with 4 vertices, textured with an image of a ring, one Model for each deformation, then drawing by picking the right model to represent each ring and setting it's transformation to the correct scale and translation. Result: 35-40 FPS
b) one model with 4 vertices, textured with an image of a ring, then for each ring setting it's matrix to represent the deformation and the scale and translation. Result: 35-40 FPS
c) one model with ~100 vertices, no texture, then for each ring setting it's matrix to represent the deformation and the scale and translation.Result: 40-44 FPS

With Sphere v1's api:
I used an image of a ring and used transformBlit to draw it with the necessary deformation, scale and translation, Result: 60-61 FPS

I note that all of the above tests were with the framerate set to 60. If I use an unlimited framerate it would go up to ~100 even with the slower Galileo methods, transform blit still would score higher but it's not so relevant at that point, this implies there is something wrong with the way minisphere limits a framerate in that it results in a significant extra slow down

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Sphere v2 API discussion
Reply #62

2. I was trying to use logger.js earlier but couldn't get it to work:
const logger = require("logger");

var log = new logger.Logger("some file");
-> cannot construct undefined


The Sphere Runtime API got overhauled and simplified in miniSphere 4.6.  See documentation:
https://github.com/fatcerberus/minisphere/blob/v4.6.0/docs/sphere2-hl-api.txt#L561-L591

For the lazy ;)
Code: (javascript) [Select]

const Logger = require('logger');
var log = new Logger('~/logFile.txt');


Quote

3. I tried copy and pasting the code into my script then ran into problems with read/write access - the "user safe file directory" was not defined so I couldn't use ~/ and the game's folder isn't writeable so I couldn't use @/ or just a path without a prefix.


Using ~ requires you to set a saveID in the game's JSON file.  If you still need to use the Sphere 1.x editor you can have an SGM and JSON file at the same time and miniSphere will prefer the latter.

Quote

4. I've being doing some tests with galileo, now some of what it can do is great BUT, I tried having it run brownian motion and it seems slower than using transformBlit. I'm talking about drawing 400-1000 rings on screen, each one spinning (deformation of shape), moving in x-y, (translation) and moving in z (scaling).

With Galileo I tried doing these as:
(results based on modelling brownian motion with 800 rings on screen)
a) an array of models each with 4 vertices, textured with an image of a ring, one Model for each deformation, then drawing by picking the right model to represent each ring and setting it's transformation to the correct scale and translation. Result: 35-40 FPS
b) one model with 4 vertices, textured with an image of a ring, then for each ring setting it's matrix to represent the deformation and the scale and translation. Result: 35-40 FPS
c) one model with ~100 vertices, no texture, then for each ring setting it's matrix to represent the deformation and the scale and translation.Result: 40-44 FPS

With Sphere v1's api:
I used an image of a ring and used transformBlit to draw it with the necessary deformation, scale and translation, Result: 60-61 FPS

I note that all of the above tests were with the framerate set to 60. If I use an unlimited framerate it would go up to ~100 even with the slower Galileo methods, transform blit still would score higher but it's not so relevant at that point, this implies there is something wrong with the way minisphere limits a framerate in that it results in a significant extra slow down


Hmm... this one is difficult to diagnose without actually seeing your code.  I have a sneaking suspicion it might be due to all the shader and matrix changes I mentioned in the miniSphere thread, though.  It definitely shouldn't be slower, that needs to get fixed ASAP ;)
  • Last Edit: July 07, 2017, 08:00:09 pm by Fat Cerberus
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Rhuan
  • [*][*][*][*]
Re: Sphere v2 API discussion
Reply #63
Thanks for the replies.

File I/O
I can't see anything in the documentation about needing a .json manifest in order to have a save file directory available, that said I am looking at migrating everything to v2 (when I'm happy with each part of v2 working properly) but I definitely want to be able to write to my game's directory - I want to be able to do weird setup scripts for one :p and I also want to be able to keep save data with the game rather than somewhere else, so I think I'll have to stick to the v1 file I/O stuff unless you can add an option for that in future.

Galileo
I note I tried the Galileo tests with my custom shader applying colour masks and also with no shader specified and there was no significant change in speed.

The impact of using FlipScreen and no SetFrameRate call vs either calling SetFrameRate or using screen.flip was pretty significant though. Target frame rate of 60 and it would only mange to do 35-45, remove the target and suddenly it could do 70+ even 100 in some of the tests.
  • Last Edit: July 07, 2017, 08:17:31 pm by Rhuan

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Sphere v2 API discussion
Reply #64

File I/O
I can't see anything in the documentation about need a .json manifest in order to have a save file directory available, that said I am looking at migrating everything to v2 (when I'm happy with each part of v2 working properly) but I definitely want to be able to write to my game's directory - I want to be able to do weird setup scripts for one :p and I also want to be able to keep save data with the game rather than somewhere else, so I think I'll have to stick to the v1 file I/O stuff unless you can add an option for that in future.


The game directory is read-only for consistency more than anything.  If you make an SPK package, the engine is unable to write to the SPK and needs to redirect the write elsewhere (i.e. into the user's home directory, like where ~/ points).  Sphere v1 does exactly that; however I suspected it'd be confusing for less experienced users that, if they make a loose directory, the files get saved with the game, but if it's an SPK, the changes are virtualized and are lost when the SPK is copied to a different machine.  And there's also the issue of Cell being the preferred workflow for Sphere v2: It's natural when using a build tool to nuke the build directory every once in a while, I do this quite often myself with miniSphere, especially to get rid of any detritus before a release.  If your game is writing data to the game directory and you do that, all your test saves and other such data get wiped too.  Enforcing save data in ~/ is inconvenient, but avoids mishaps.

Overall, I decided when I was designing SphereFS that the way it's implemented now is for the best.  The sandboxing is annoying for more advanced users, but it prevents novice and intermediate users from shooting themselves in the foot.

Quote

Galileo
I note I tried the Galileo tests with my custom shader applying colour masks and also with no shader specified and there was no significant change in speed.

The impact of using FlipScreen and no SetFrameRate call vs either calling SetFrameRate or using screen.flip was pretty significant though. Target frame rate of 60 and it would only mange to do 35-45, remove the target and suddenly it could do 70+ even 100 in some of the tests.


Your graphics driver might be enforcing vsync, which if the game is already chugging, will just make the situation worse.  In any case I'll look into it when I get a chance.
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Sphere v2 API discussion
Reply #65
You're right, by the way, that saveID isn't mentioned in the documentation.  I added it to the release notes for miniSphere 4.5, but it seems I never documented it.  Sorry for the inconvenience! :-[
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Sphere v2 API discussion
Reply #66

I can't see anything in the documentation about needing a .json manifest in order to have a save file directory available


I fixed that, see:
https://github.com/fatcerberus/minisphere/blob/master/docs/sphere2-core-api.txt#L80-L89

:)

saveID wasn't required before miniSphere 4.5--all games shared the same save directory.  But that made it difficult to write library code that used the save store.
  • Last Edit: July 08, 2017, 10:23:58 am by Fat Cerberus
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Rhuan
  • [*][*][*][*]
Re: Sphere v2 API discussion
Reply #67
I still want the ability to edit documents in the game's folder from within the api - maybe after setting a special mode or something to avoid mistakes? (One practical use is the ability to change the game's default screen res you ought to be able to edit the game's manifest to do that, but there are a stack of other uses too)


Anyhow, for now the graphics speed issue, I've cleaned up my script so you can run and it see what you think. couple of notes: I switched the v1 function from transformBlit to transformBlitMask and now it's speed is closer to the v2 implementations - though it's still slightly faster conversely the v2 versions don't seem to have any speed difference for having a custom shader that does masking vs not having one.

Sorry about some of the variable names, was in an odd mood when writing this, also I used tab and space interchangeably so some of the indenting is a bit weird, should look normal if pasted into the sphere editor or any text editor where tab = 2 spaces.

Change the values set to the variables at the top to try out the different options, note I think modes 1 and 2 are just bad ideas and that the comparison should be between modes 0 and 3.

Code: [Select]
//what mode
//0 = shape + model per deformation, with 4 vertices per shape and one texture shared by all
//1 = one shape + model only but with 4 vertices and a texture
//2 = one shape + model only but with points (variable  below) number of vertices and no texture
//3 = v1 .transformBlitMask
var mode =0;
var points = 1000; // how many points on a ring
var num_rings = 1000; //how many rings to draw
var speed_limit = false; //true to use screen.flip (60 FPS limt) false to use FlipScreen - no limit

//load shader - set to Shader.Default if you don't have shaders to use
//note script below will pass shader 4 floats m_r, m_g, m_b and m_a for color masking
//tests show that using the Default shader (and hence no colours in modes 0,1 and 2 has no noticeable speed impact vs using custom vertex shader that masks
var shades = new Shader({vertex: "shaders/customised.vert.glsl", fragment: "shaders/customised.frag.glsl"});


//function to set up rings
function generate_rings(how_many,min_x,max_x,min_y,max_y)
{
  var rings = new Array();
  for(var i = 0; i < how_many; ++i)
  {
    rings[i]    = new Object();
    rings[i].x  = Math.floor(Math.random()*(max_x-min_x))+ min_x;
    rings[i].y  = Math.floor(Math.random()*(max_y-min_y))+ min_y;
    rings[i].z  = 0.6 - (2 * Math.random() / 5);
    rings[i].t  = Math.floor(Math.random()*100);
    rings[i].dx = Math.round(10 * (Math.random() - 0.5));
    rings[i].dy = Math.round(7  * (Math.random() - 0.5));
    rings[i].dz = 0.1  * (Math.random() - 0.5);
    rings[i].dt = 5 - Math.ceil(10 * Math.random());
    rings[i].col = [1,1,1,0.9];
    rings[i].dc  = [(1-Math.round(Math.random()))*Math.random()*0.01,(1-Math.round(Math.random()))*Math.random()*0.01,(1-Math.round(Math.random()))*Math.random()*0.01,(1-Math.round(Math.random()))*Math.random()*0.01];
  }
  return rings;
}

//function to update and draw the rings
function update_random_rings(rings,update,min_x,max_x,min_y,max_y)
{
  for(var i = 0; i < rings.length; ++i)
  {
    if(update)
    {
      if(rings[i].t + rings[i].dt > 99)
      {
        rings[i].t = 0;
      }
      else if(rings[i].t + rings[i].dt <0)
      {
        rings[i].t = 99;
      }
      else
      {
        rings[i].t += rings[i].dt;
      }
      rings[i].x += rings[i].dx;
      rings[i].y += rings[i].dy;
      rings[i].z += rings[i].dz;
      if (rings[i].x > max_x)
      {
        rings[i].dx = - Math.round(4 * Math.random());
      }
      else if (rings[i].x < min_x)
      {
        rings[i].dx =   Math.round(4 * Math.random());
      }
      else if(Math.random() > 0.99)
      {
        rings[i].dx -= Math.round(3 * (Math.random() - 0.5));
      }
      if (rings[i].y > max_y)
      {
        rings[i].dy = - Math.round(3 * Math.random());
      }
      else if (rings[i].y < min_y)
      {
        rings[i].dy =   Math.round(1.5 * Math.random());
      }
      else if (Math.random() > 0.99)
      {
        rings[i].dy -= Math.round(1.5 * (Math.random() - 0.5));
      }
      if (rings[i].z <0.2)
      {
        rings[i].dz = 0.02  * (Math.random());
      }
      else if (rings[i].z > 1.7)
      {
        rings[i].dz = - 0.02  * (Math.random());
      }
      else if (Math.random() > 0.99)
      {
        rings[i].dz -= 0.005  * (Math.random() - 0.5);
      }
      if(rings[i].dt > 4)
      {
        rings [i].dt = 2;
      }
      else if(rings[i].dt <-4)
      {
        rings [i].dt = -2;
      }
      else if(Math.random() >0.95)
      {
        rings[i].dt += 1 - Math.ceil(Math.random()*2)
      }
      rings[i].col[0] = (rings[i].col[0] + rings[i].dc[0]) % 1;
rings[i].col[1] = (rings[i].col[1] + rings[i].dc[1]) % 1;
rings[i].col[2] = (rings[i].col[2] + rings[i].dc[2]) % 1;
rings[i].col[3] = (rings[i].col[3] + rings[i].dc[3]) % 1;


    }
    if(mode < 3)
    {
      foo.identity();
   
      if(mode > 0)
      {
        foo.matrix[1][0] = 3 * (Math.sin(rings[i].t * Math.PI/45) - Math.cos(rings[i].t * Math.PI/45)) / 8;
        foo.matrix[1][1] = 0.5 - Math.sin(rings[i].t * Math.PI/45) - Math.cos(rings[i].t * Math.PI/45);
        foo.matrix[1][2] = 3 * Math.cos(rings[i].t * Math.PI/45);
        model_.setFloat("m_r", rings[i].col[0]);
        model_.setFloat("m_g", rings[i].col[1]);
        model_.setFloat("m_b", rings[i].col[2]);
        model_.setFloat("m_a", rings[i].col[3]);
      }
      else
      {
    models[rings[i].t].setFloat("m_r", rings[i].col[0]);
    models[rings[i].t].setFloat("m_g", rings[i].col[1]);
    models[rings[i].t].setFloat("m_b", rings[i].col[2]);
    models[rings[i].t].setFloat("m_a", rings[i].col[3]); 
      }
   
      foo.scale(rings[i].z / 2,rings[i].z /1.5);   
      foo.translate(rings[i].x, rings[i].y);  

   
  if(mode > 0)
  {
    foo.translate(0, 50 * Math.cos(rings[i].t * Math.PI/100)); 
    model_.draw(screen);
  }
  else
  {
        models[rings[i].t].draw(screen);
      }
    }
    else
    {
      jeff.transformBlitMask(rings[i].x ,
                             rings[i].y + (rings[i].z * 25)                    * Math.cos(rings[i].t * Math.PI/50),
                             rings[i].x +  rings[i].z * 80,
                             rings[i].y + (rings[i].z * 25)                    * Math.sin(rings[i].t * Math.PI/50),
                             rings[i].x +  rings[i].z * 80,
                             rings[i].y +  rings[i].z * 12.5 - rings[i].z * 25 * Math.cos(rings[i].t * Math.PI/50),
                             rings[i].x,
                             rings[i].y +  rings[i].z * 12.5 - rings[i].z * 25 * Math.sin(rings[i].t * Math.PI/50),CreateColor(rings[i].col[0]*255,rings[i].col[1]*255,rings[i].col[2]*255,rings[i].col[3]*255));

    }
  }
}

//create the ring image
function ring(colour_a, colour_b, a, b)
{
  if(mode < 3)
  {
    var output = new Surface(2*a + 2, 2*b + 2, new Color(0,0,0,0)); 
  }
  else
  {
    var output = CreateSurface(2*a+2,2*b+2,CreateColor(0,0,0,0));
  }
 
  var _vertices = [];
  for(var i = 0, t_x = 0, t_y = 0; i < (points/5); ++i)
  {
    t_x = a * Math.cos(i*Math.PI/(points/10)) + 1;
    t_y = b * Math.sin(i*Math.PI/(points/10)) + 1;
   
    _vertices.push({x:t_x,y:t_y,color:colour_a});
    _vertices.push({x:t_x + 1,y:t_y,color:colour_b});
    _vertices.push({x:t_x,y:t_y + 1,color:colour_b});
    _vertices.push({x:t_x -1,y:t_y,color:colour_b});
    _vertices.push({x:t_x,y:t_y-1,color:colour_b});
  }
 
  if(mode <3)
  {
    foo.translate(a,b);
    var temp = new Shape(_vertices,null,ShapeType.LineLoop);

    temp.draw(output,foo);
    if(mode < 2)
    {
      return output.toTexture();
    }
    else
    {
      return new Model([temp],shades);
    }
  }
  else
  {
    for(i = 0; i < _vertices.length; ++i)
    {
      output.setPixel(_vertices[i].x + a, _vertices[i].y + b, CreateColor(_vertices[i].color.r*255,_vertices[i].color.g*255,_vertices[i].color.b*255));
    }
    return output.createImage();
  }
}




var fun_rings =[];

fun_rings[0] = generate_rings(Math.floor(num_rings/4),0,400,0,357);
fun_rings[1] = generate_rings(Math.floor(num_rings/4),0,400,377,748);
fun_rings[2] = generate_rings(Math.floor(num_rings/4),600,1024,0,357);
fun_rings[3] = generate_rings(Math.floor(num_rings/4),600,1024,377,748);


var coords = [[50,400,20,357],[50,400,377,730],[500,1000,377,730],[500,1000,0,357]];



var foo = new Transform();

var jeff = ring(new Color(1,1,1,1),new Color(0.9,0.9,0.9,1),65,30);


//create the models for the rings type of model determined by mode
if (mode == 0)
{
  var shapes = [];
  var models = [];

  var y_s =[];
  var v_s = [];
  for (var inc = 0; inc < 100; ++inc)
  {
    y_s[0] = Math.round(Math.cos(inc * Math.PI/50) * 30);
    y_s[1] = Math.round(Math.sin(inc * Math.PI/50) * 30);
    y_s[2] = Math.round(15 - Math.sin(inc * Math.PI/50) * 30);
    y_s[3] = Math.round(15 - Math.cos(inc * Math.PI/50) * 30);

    shapes[inc] = new Shape([{x:0, y:y_s[0],u:0,v:0,color:Color.White},
                             {x:80,y:y_s[1],u:1,v:0,color:Color.White},
                             {x:0, y:y_s[2],u:0,v:1,color:Color.White},
                             {x:80,y:y_s[3],u:1,v:1,color:Color.White}],jeff,ShapeType.TriStrip);
                            


    models[inc] = new Model([shapes[inc]],shades);
    models[inc].setInt("mask_mode", 1);
    models[inc].transform = foo;
         
  }
}
else if (mode == 1)
{
  var shape_ = new Shape([{x:0,y:0,u:0,v:0,color:Color.White},
                             {x:80,y:0,u:1,v:0,color:Color.White},
                             {x:0,y:30,u:0,v:1,color:Color.White},
                             {x:80,y:30,u:1,v:1,color:Color.White}],jeff,ShapeType.TriStrip);
  var model_ = new Model([shape_],shades);
  model_.setInt("mask_mode", 1);
  model_.transform = foo;
}
else if(mode == 2)
{
  model_ = jeff;
  model_.setInt("mask_mode", 1);
  model_.transform = foo;
}


//loop to draw rings etc
var time = GetTime();
var time1 = GetTime();
var flag = false;
var ticker = 0;
while(!IsKeyPressed(KEY_ESCAPE))
{
  if(time + 25 < GetTime())
  {
    time = GetTime();
    flag = true;
  }
  if(time1 + 2000 < GetTime())
  {
    time1 = GetTime();
    if(Math.random() > 0.7)
    {
      ++ticker;
    }
  }
  for(i=0;i<4;++i)
  {
    update_random_rings(fun_rings[i],flag,coords[(ticker + i) % 4][0],coords[(ticker + i) % 4][1],coords[(ticker + i) % 4][2],coords[(ticker + i) % 4][3])
  }
flag = false;
while(IsAnyKeyPressed()&&(!IsKeyPressed(KEY_ESCAPE)));
  if(speed_limit)
  {
  screen.flip(); 
  }
  else
  {
    FlipScreen();
  }
}
  • Last Edit: July 08, 2017, 11:18:44 am by Rhuan

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Sphere v2 API discussion
Reply #68

One practical use is the ability to change the game's default screen res you ought to be able to edit the game's manifest to do that


This is a popular Sphere v1 hack, however Sphere v2 lets you directly change screen.width and screen.height at runtime to set the resolution - and you don't need to restart the game!  I'd be iffy of self-modifying games, as that's an easy way to destroy data just in the course of normal testing.  And if you're using Cell or some other build process, you'd still need to write your code with the assumption that the modifications aren't persistent - because you might at some point decide to blow away the build directory (and if you're using git, the build directory wouldn't be committed at all).  It's just a really easy way to introduce bugs all around and I'd prefer to avoid leaving such a dangerous footgun lying around.

That said, I'd probably be willing to implement some sort of "dev mode" that could be enabled in the manifest which would allow things like absolute paths, write access to the game directory, etc. that are normally denied.  However the default would remain the strict sandboxed mode, and I would heavily recommend to not enable devmode for redistributed games.

Anyway I'll look at the graphics code to see if I can find what's wrong.

By the way: screen.flip() can be unthrottled by setting
Code: (javascript) [Select]

screen.frameRate = Infinity;
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Sphere v2 API discussion
Reply #69
@Rhuan: I posted a GitHub issue about allowing a writable game directory so I don't forget all about it in a week ;)
https://github.com/fatcerberus/minisphere/issues/183
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Sphere v2 API discussion
Reply #70
I tested your code; Galileo is indeed significantly slower.  Results when using Shader.Default with speed_limit = false and vsync disabled, by mode:

  • mode 0: 87-91 FPS, nothing is displayed

  • mode 1: 61 FPS, nothing is displayed

  • mode 2: 64 FPS, all rings are white

  • mode 3: 155-160 FPS, colored rings



Considering that transformBlitMask() does this under the hood:
https://github.com/fatcerberus/minisphere/blob/v4.6.0/src/minisphere/vanilla.c#L3624-L3633

And also considering the fact that I have an overclocked GTX 1070 in this machine, 90 FPS for something this simple is abysmal.  Something is clearly wrong with my Galileo implementation.  I'll need to do some profiling to figure out what's causing the performance issues.

edit:
Galileo Model#draw() under the hood:
https://github.com/fatcerberus/minisphere/blob/v4.6.0/src/minisphere/galileo.c#L200-L247

One thing that jumps out at me immediately is that 1) Each model drawn does two shader changes, once to switch to the model's shader and again to restore the v1 shader; 2) Each model drawn does four matrix uploads: twice each (i.e. set, draw, restore) for the modelview and projection matrices; and 3) Uniforms are sent each time the model is drawn.  The Sphere v1 call does none of this because it can safely assume the correct settings are already in place, largely thanks to the above shenanigans. :P

Clearly this is going to need to be optimized better if I want anyone to actually use the new API. ;)
  • Last Edit: July 09, 2017, 02:23:23 am by Fat Cerberus
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Rhuan
  • [*][*][*][*]
Re: Sphere v2 API discussion
Reply #71
I'm confused by the lack of output for modes 0 and 1 - will recheck later and see if there's a missing line in the copy and paste.

Also can you try with speed limit set to true as well?

For shaders could you have a global variable in the engine saying what shader is in use and only update if it's not the right one? Would have to add the check to all drawing functions but I imagine that it will be quite rare to use multiple shaders so this could result in some speed increase; alternatively maybe make setting a shader a separate action to drawing? So the user has control of it through the the api.

  • Rhuan
  • [*][*][*][*]
Re: Sphere v2 API discussion
Reply #72
I just pasted the example from above into a new script, set shades to Shader.Default and ran it with modes 0 and 1 and they both drew white rings, so not sure why you got nothing displayed.

Note: Modes 0-2 should draw White rings with the default shader to get a colour output from them need colour masking in your shader but that's not relevant for the speed tests (FPS doesn't change when I use my own shader vs the default).

I've also being thinking about ways to fix the speed issues if they do relate to the points you've identified, what do you think of either:


Adding a "Draw Mode" to the api
Could you wrap the Shader and projection matrices into a "DrawMode" type object that has a default value depending on the sphere version from the manifest (either whatever v1 needs if v1 or an sgm manifest or whatever is needed for the default shader and "standard" v2 drawing is v2) - then enable a function that changes this mode - you could then remove all shader and projection setting from your draw functions. (I note that this would de-couple models and shaders and also mean the script would need a call to extra function if switching between v2 and v1 drawing - but it should drastically reduce the number of graphics card uploads the engine has to do when doing lots of drawing.

Regarding the model view matrices, do they need to be set to null each time?  if shape_draw didn't have its 3rd lineand if group_draw didn't have row 239 would each shape drawn be transformed by the combination of its own matrix and the previous ones or would it be correct (i.e. is screen_transform(g_screen, NULL); purely there to make v1 work?) - if yes it could be dumped into the proposed DrawMode above.

Alternative approach 1 - internal draw mode
Alternatively "DrawMode" could be internally tracked, with a tracker variable of some kind and then a function to do the necessary shader/matrix uploads could be called if an action is being done that's not consistent with the last one.


Alternative approach 2 - separate tracking
Have a v1/v2 mode stored in a boolean in the engine, at the start of each v1 draw function check if currently in v2 mode and if so do the necessary resets, at the end of every draw functions set the boolean to say which mode you're in. -> this should mean that several of the expensive operations are only needed when mixing v1 and v2 drawing.

Similarly you could have a variable tracking what shader is set, using some kind of ID, maybe just an int 0 for v1 shader, 1 for v2 default, 2 for the first custom one loaded 3 for the next etc. Then at the start of each v1 draw function if in v2 mode reset the shader and set the int to 0, if in v1 mode do nothing. At the start of each v2 draw function check if the correct shader is already set per the int, if yes do nothing if no set it.


Uniforms...
I don't see an easy fix for the uniform uploads, at least not without reducing the level of shader control; the valuable features you can put through a shader seem to me to be dependent on setting uniforms specific to whatever you're drawing. Particularly if I want to use them for colour masking and implementing atlases - maybe that's not what they're meant for - but they seem so amazing for that.

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Sphere v2 API discussion
Reply #73
Something like your DrawMode idea but implemented internally would probably be good.  I wouldn't want to expose something like that in the API as it's getting a little too close to the state-machine model OpenGL itself uses.  Galileo's stateless object-based model makes it much easier to reason about.

You're right about screen_transform() - by passing NULL it sets an identity matrix for the modelview (and in the next release, an orthographic projection) so that Sphere v1 works.  This is all fallout from Sphere v1 being implemented first - when I wrote the Vanilla implementation, it didn't have to worry about the correct shader/matrix/whatever being set because it was all already set up properly on engine start.  So once I added Galileo, in order for that assumption to still hold and allow v1 and v2 to be combined in the same codebase, Galileo had to be a good citizen and undo all its changes after every draw operation.  Better internal tracking (i.e. decentralizing the responsibility to "undo") would go a long way in speeding things up.
  • Last Edit: July 09, 2017, 09:51:49 am by Fat Cerberus
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Rhuan
  • [*][*][*][*]
Re: Sphere v2 API discussion
Reply #74

Something like your DrawMode idea but implemented internally would probably be good.  I wouldn't want to expose something like that in the API as it's getting a little too close to the state-machine model OpenGL itself uses.  Galileo's stateless object-based model makes it much easier to reason about.
So something like one of my "Alternate approach" ideas above, possible Alternative Approach 2?

I have been thinking though - will Galileo ever be faster than v1 drawing for simple items assuming we want to be able to move them and recolour them.

Let's compare what TransformBlitMask has to do to what Galileo has to do:

TransformBlitMask has to:
1. Have an image/texture - this will have been uploaded already so nothing to do at draw time.
2. Upload a shape with 4 vertices.
3. Texture the shape with the pre-uploaded texture.
4. Draw.

Galileo has to:
1. Have an image/texture, as above this will have been pre-uploaded.
2. Have a shape with 4 vertices - this will also have been pre-uploaded.
3. Texture the shape with the image.
4. Specify a shader (that's going to do the masking) - following the discussed changes this will hopefully be pre-set
5. Upload a matrix to specify the position.
6. Upload uniforms to specify the colour.
7. Draw

Now assuming the optimisations discussed above are done the minimum actions at draw time will become:

TransformBlitMask:
1. Upload a shape with 4 vertices.
2. Texture the shape with the pre-uploaded texture.
3. Draw.

Galileo:
1. Texture the shape with the image.
2. Upload a matrix to specify the position.
3. Upload uniforms to specify the colour.
4. Draw

So it boils down to whether uploading a shape with 4 vertices is faster or slower than uploading a matrix and some uniforms. (I note that Galileo will likely race ahead for drawing complex objects containing many vertices or models comprised of multiple shapes where the matrix and uniforms can be shared - but frequently a game situation will require objects that move independently of each other so it's hard to say which situation is more common)