cbiffle / ruckus

Procedural CAD for Weirdos
Other
50 stars 6 forks source link

Simple sphere example doesn't work on MacOS 10.12.3, Racket v6.8 #2

Open yminsky opened 7 years ago

yminsky commented 7 years ago

I tried the most basic example (making just a sphere), and it failed with the following output:

$ ./ruckus-3d ~/Documents/sphere.rkt  2>&1 > ~/failure
generate-compile-and-link: error compiling: ERROR: 0:1: '' :  version '130' is not supported
ERROR: 0:105: 'uint' : syntax error: syntax error

  context...:
   /Users/yminsky/Library/Racket/6.8/pkgs/ruckus/viz/viewer.rkt:55:8
   /Applications/Racket v6.8/collects/racket/private/more-scheme.rkt:148:2: call-with-break-parameterization
   /Applications/Racket v6.8/collects/ffi/unsafe/atomic.rkt:72:13
   /Applications/Racket v6.8/share/pkgs/gui-lib/mred/private/wx/common/canvas-mixin.rkt:144:4: do-on-paint method in ...mon/canvas-mixin.rkt:118:2
   /Applications/Racket v6.8/share/pkgs/gui-lib/mred/private/wx/cocoa/panel.rkt:71:6: for-loop
   /Applications/Racket v6.8/share/pkgs/gui-lib/mred/private/wx/cocoa/panel.rkt:70:4: paint-children method in ...e/wx/cocoa/panel.rkt:37:2
   /Applications/Racket v6.8/share/pkgs/gui-lib/mred/private/wx/cocoa/panel.rkt:71:6: for-loop
   /Applications/Racket v6.8/share/pkgs/gui-lib/mred/private/wx/cocoa/panel.rkt:70:4: paint-children method in ...e/wx/cocoa/panel.rkt:37:2
   /Applications/Racket v6.8/share/pkgs/gui-lib/mred/private/wx/cocoa/frame.rkt:401:4: show method in frame%
   /Applications/Racket v6.8/collects/racket/private/more-scheme.rkt:148:2: call-with-break-parameterization
   /Applications/Racket v6.8/collects/ffi/unsafe/atomic.rkt:72:13
   (submod /Users/yminsky/Library/Racket/6.8/pkgs/ruckus/viz/view3d.rkt main): [running body]
bin $ ./ruckus-3d ~/Documents/sphere.rkt  2>&1
Loading opengl bindings...
cpu time: 286 real time: 287 gc time: 118
Creating viewer window...
Recompiling design at /Users/yminsky/Documents/sphere.rkt
Design contains 1 nodes.
#version 130

const float PI = 3.14159265358979323846;
const float ISOSURFACE = 0.0;
const vec4 BACKGROUND = vec4(0.1, 0.1, 0.3, 1);

uniform vec2 resolution;
uniform sampler2D nodeColors;

float smin(float k, float a, float b) {
    float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
    return mix(b, a, h) - k * h * (1.0 - h);
}

////////////////////////////////////////////////////////////////////////////////
// Quaternion support.  Note that quaternions are represented
// with the vector portion in xyz and the scalar in w.

// Conjugate.
vec4 qconj(vec4 q) {
  return vec4(-q.xyz, q.w);
}

// Quaternion multiplication.
vec4 qmul(vec4 p, vec4 q) {
  return vec4(p.w * q.xyz + q.w * p.xyz + cross(p.xyz, q.xyz),
              p.w * q.w - dot(p.xyz, q.xyz));
}

// Rotate vector by quaternion, giving new vector.
vec3 qrot(vec4 q, vec3 v) {
  return qmul(qmul(q, vec4(v, 0)), qconj(q)).xyz;
}

////////////////////////////////////////////////////////////////////////////////
// Primitive distance field evaluators.  These are defined here and invoked
// from generated code to reduce duplication, because while I'm pretty
// confident that GLSL compilers can inline, I'm not sure they can extract
// functions.

float dfSphere(float radius, vec3 p) {
  return length(p) - radius;
}

float dfCircle(float radius, vec3 p) {
  return length(p.xy) - radius;
}

float dfBox(vec3 corner, vec3 p) {
  vec3 d = abs(p) - corner;
  return min(max(d.x, max(d.y, d.z)), 0.)
       + length(max(d, 0.));
}

float dfRect(float w, float h, vec3 p) {
  vec2 corner = vec2(w, h);
  vec2 d = abs(p.xy) - corner;
  return min(max(d.x, d.y), 0.)
       + length(max(d, 0.));
}

float dfCapsule(float halfh, float r, vec3 q) {
  q = abs(q);
  float t = clamp((q.z * halfh) / (halfh * halfh), 0.0, 1.0);
  return length(q - vec3(0, 0, halfh * t)) - r;
}

vec3 radialProject(vec3 pos, float period, float shift) {
  float a = pos.x == 0. ? (sign(pos.y) * (PI / 2.)) : atan(pos.y, pos.x);
  float d = length(pos.xy);
  float a_ = mod(a + (period/2.), period) - (period/2.) + shift;
  return vec3(d * cos(a_),
              d * sin(a_),
              pos.z);
}

////////////////////////////////////////////////////////////////////////////////
// Global distance field evaluator.

// Prototype; generated at runtime and appended to this file.
float distanceField(vec3 r0);

// Computes the distance field gradient using the method of central
// differences, which also happens to approximate the normal of a
// nearby surface.  We use it for lighting calculations.
vec3 distanceFieldNormal(vec3 pos, float eps) {
  // TODO: should this actually depend on the intersection threshold?
  float h = eps / 2.;

  vec3 g = vec3(
    distanceField(pos + vec3(h, 0, 0)) - distanceField(pos - vec3(h, 0, 0)),
    distanceField(pos + vec3(0, h, 0)) - distanceField(pos - vec3(0, h, 0)),
    distanceField(pos + vec3(0, 0, h)) - distanceField(pos - vec3(0, 0, h)));

  return normalize(g);
}

////////////////////////////////////////////////////////////////////////////////
// Object discrimination.

// Prototype; generated at runtime and appended to this file.
uint nearestNodeId(vec3 r0);

vec3 matColor(uint nid) {
  return texelFetch(nodeColors, ivec2(nid % 512u, nid / 512u), 0).rgb;
}

////////////////////////////////////////////////////////////////////////////////
// False-color visualization support.

// Converts an HSV color to its RGB equivalent.  Generating false colors in
// HSV is *far* more convenient, so we pay the (low) cost.
// Source: http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
vec3 hsv2rgb(vec3 c) {
  vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
  vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
  return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}

// Generates a color intended to make gradations between 0 and 1 more obvious.
// The current implementation wraps around above 1, so you may want to clamp.
vec4 falseColor(float level) {
  return vec4(hsv2rgb(vec3(level + (120. / 360.), 1, 1)), 1);
}
const vec3 LIGHT = vec3(0, 0, 100);
const float TRUST = 1.0;

const float i_a = 0.3;
const float i_d = 0.6;
const float i_s = 0.4;

const float k_a = 0.6;
const float k_d = 0.7;
const float k_s = 0.8;
const float alpha = 8.;

uniform vec4 orientation;
uniform float zoom;
uniform float closeEnough;
uniform int stepLimit;
uniform bool showComplexity;
uniform bool showDistance;

out vec4 fragColor;

void main() {
  // Paperwork!

  // Clipping plane Z coordinates.
  float near =  1500.;
  float far  = -1500.;

  // TODO: camera Z needs to depend on clip plane positions!
  vec3 cameraPosition = vec3(0, 0, near + near);

  // Position of pixel being traced on the near clip plane.
  vec3 pos = vec3(gl_FragCoord.xy - resolution.xy / 2., near);
  vec3 npos = pos / resolution.x;

  // Direction in which this ray will advance.
  vec3 dir = normalize(pos - cameraPosition);
  // Shorthand for the object-to-eye vector, used for specular reflection.
  vec3 toEye = qrot(orientation, -dir);

  // Initialized with black to handle the "too many steps" case, this will
  // be overwritten if the ray either hits a surface, or escapes past the
  // far clipping plane.
  vec4 color = vec4(0, 0, 0, 1);

  // Rotate the light position into object space.
  vec3 rLight = qrot(orientation, LIGHT);

  // Ray-marching loop!

  int stepsTaken = 0;
  float d = 0.;
  for (int steps = 0; steps < stepLimit; ++steps) {
    // GLSL won't let us use an externally declared iteration variable, so
    // we copy 'steps' to 'stepsTaken' at each iteration so that we may use
    // it later to show march complexity.
    stepsTaken = steps;

    // Early-exit the marching process at the far clip plane.  This improves
    // performance significantly when software rendering with Mesa; unclear
    // whether it helps on hardware.
    if (pos.z < far) {
      color = BACKGROUND;
      break;
    }

    // Transform the ray's position into object space.
    // TODO: couldn't we just do the marching in object space?  Sure, it
    // makes the far clip plane check more expensive, but it's just a plane
    // test....
    vec3 tpos = qrot(orientation, pos) / zoom;

    d = (distanceField(tpos) - ISOSURFACE) * zoom;
    if (d <= closeEnough) {
      // Hit!
      uint nid = nearestNodeId(tpos);
      vec3 normal = distanceFieldNormal(tpos, closeEnough);
      vec3 nl_m = normalize(rLight);
      vec3 r_m = -reflect(nl_m, normal);

      float ambient = i_a * k_a;
      float diffuse = i_d * k_d * max(dot(nl_m, normal), 0.);
      float spec = i_s * k_s * pow(max(dot(r_m, toEye), 0.), alpha);

      vec3 light = matColor(nid) * (ambient + diffuse) + spec;
      color = vec4(light, 1);
      break;
    } else {
      // Keep on marching.  The TRUST ratio is used to de-rate the distance
      // estimate if we think it may not be a true lower bound.  TBD.
      pos += d * TRUST * dir;
    }
  }

  // At exit from the loop we've either hit, in which case 'color' is set,
  // or flown off into space, in which case we've got the background color.
  // In the latter case, 'd' is basically random.

  // Compute the alternate viz modes.
  vec4 complexity = falseColor(float(stepsTaken) / float(stepLimit));
  vec4 distance = falseColor(clamp(d / closeEnough, 0., 1.));

  fragColor = sqrt(showComplexity ? complexity
                                  : showDistance ? distance : color);
}

float distanceField(vec3 r0) {
  float r1 = dfSphere(100.0, r0);
  return r1;
}
uint nearestNodeId(vec3 r0) {
  uint r2 = 0u;
  return r2;
}
generate-compile-and-link: error compiling: ERROR: 0:1: '' :  version '130' is not supported
ERROR: 0:105: 'uint' : syntax error: syntax error

  context...:
   /Users/yminsky/Library/Racket/6.8/pkgs/ruckus/viz/viewer.rkt:55:8
   /Applications/Racket v6.8/collects/racket/private/more-scheme.rkt:148:2: call-with-break-parameterization
   /Applications/Racket v6.8/collects/ffi/unsafe/atomic.rkt:72:13
   /Applications/Racket v6.8/share/pkgs/gui-lib/mred/private/wx/common/canvas-mixin.rkt:144:4: do-on-paint method in ...mon/canvas-mixin.rkt:118:2
   /Applications/Racket v6.8/share/pkgs/gui-lib/mred/private/wx/cocoa/panel.rkt:71:6: for-loop
   /Applications/Racket v6.8/share/pkgs/gui-lib/mred/private/wx/cocoa/panel.rkt:70:4: paint-children method in ...e/wx/cocoa/panel.rkt:37:2
   /Applications/Racket v6.8/share/pkgs/gui-lib/mred/private/wx/cocoa/panel.rkt:71:6: for-loop
   /Applications/Racket v6.8/share/pkgs/gui-lib/mred/private/wx/cocoa/panel.rkt:70:4: paint-children method in ...e/wx/cocoa/panel.rkt:37:2
   /Applications/Racket v6.8/share/pkgs/gui-lib/mred/private/wx/cocoa/frame.rkt:401:4: show method in frame%
   /Applications/Racket v6.8/collects/racket/private/more-scheme.rkt:148:2: call-with-break-parameterization
   /Applications/Racket v6.8/collects/ffi/unsafe/atomic.rkt:72:13
   (submod /Users/yminsky/Library/Racket/6.8/pkgs/ruckus/viz/view3d.rkt main): [running body]
cbiffle commented 7 years ago

Hello Yaron!

It seems like your system doesn't support GLSL 1.30. I'm a bit boggled as to why... it's been supported on MacOS for several years now. I don't have any Macs for testing, and some Googling hasn't provided any easy answers.

I know Apple has been pushing graphics APIs forward pretty aggressively. It's possible that GLSL 1.30 is too old for this system. I'm using it as a lingua franca, but I might need to reconsider that.

yminsky commented 7 years ago

That is odd. These seem vaguely relevant:

http://stackoverflow.com/questions/20264814/glsl-version-130-on-mac-os-x-causes-error http://stackoverflow.com/questions/31803872/opengl-glsl-shaders-on-mac-does-not-compile

but I don't see a clear answer from either one.

cbiffle commented 7 years ago

That does seem relevant, particularly the bit about Mac OS not supporting GL compatibility profiles. I played with getting Ruckus to request a Core profile explicitly back in December 2015; I believe I ran into a limitation in the Racket GL bindings.

And yet I'm fairly certain that I tested Ruckus on Mac around that time...

Let me see if I can find anyone in the center of the "Racket user - Mac owner - CAD user" Venn diagram nearby.

soegaard commented 7 years ago

FWIW I am getting the same error as yminsky. This is on macOS Sierra, the graphics card is a "Intel Iris Graphics 6100 1536 MB".

soegaard commented 7 years ago

Without knowing anything about GLSL, I have attempted the following:

These changes are enough to remove errors from GL.

Alas, now I get an error from Racket:

link: bad variable linkage;
 reference to a variable that is not a procedure or structure-type constant across all instantiations
  reference phase level: 0
  variable module: " 
 /Users/soegaard/Dropbox/GitHub/racket/racket/share/pkgs/ruckus/viz/glsl.rkt"
variable phase: 0
reference in module: 
"/Users/soegaard/Dropbox/GitHub/racket/racket/share/pkgs/ruckus/viz/spheretrace-viewer.rkt" 
in: node->glsl-distance
alanbernstein commented 5 years ago

I'd love to try out Ruckus, but I'm running into the same problem.

cbiffle commented 5 years ago

It's probably going to get worse -- my understanding is that Apple has deprecated OpenGL support, with the intent of removing it soon. As I cannot program against their proprietary alternative (because I don't have a Mac), Ruckus (and all Racket GL programs) will stop working completely.

Given this, it might not be worth debugging. :-(

alanbernstein commented 5 years ago

Thanks for the update, I'll try it out in Linux.

zen3d commented 5 years ago

@soegaard got most of the needed changes but not all of them.

I forked this repo to zen3d/ruckus, and it now runs on MacOS.

Primary changes: 1) replace all "uint" to "int", 2) remove "u" from uint literals, 3) don't append "u" to number code generation, 4) change GLSL version to 120, 5) eliminate references to "out vec4 fragColor;", 6) change "fragColor" assignments to "gl_FragColor", and 7) replace "nid % 512" with an equivalent expression that works in GLSL 1.2.

That said, I haven't tested these changes on any other machines or other OS variants.

If @cbiffle wants a diff to incorporate the changes, I will gladly provide them. But if not, I will proceed to make further enhancements to my fork. The implication of this change is that more machines will run ruckus at the cost of potentially some performance impact on higher end systems. How much impact is TBD.

My objective is to get feature parity with OpenScad, and the current state of ruckus is mostly there except for polygons and polyhedra (that is probably an over-simplification, but it's good enough for me).

zen3d commented 5 years ago

I just verified that my changes also work on linux (specifically, Linux Mint Debian Edition, although that shouldn't matter) with an NVidia GTX 1080 driven by NVidia's closed source proprietary drivers. It is a bit faster on some of the slower demos, but most demos run as fast as my MacBook Pro with an NVidia GT 750M GPU. Not that most of the demos push the performance envelop.

On the other hand, they don't work remotely via vanilla VNC or RDP, but then again, 3D via remote access is generally broken regardless of platform, at least with those two protocols. There are ways around that limitation, but you have to jump through a bunch of hoops. So no surprises there.

As always, YMMV.

PS The failure mode for remote access is that racket doesn't find a new enough version of glx, FWIW. I'm guessing this might have something to do with the way linux emulates a GPU in software when it provides remote access, but that's just a guess.

soegaard commented 5 years ago

@zen3d Thanks! I can confirm that the changes work on macOS.

zen3d commented 5 years ago

@soegaard Thanks for that confirmation, Jens.