- Add
Image::buildfunction to build an image from aVec<T>andImageSizeLike, returning an error if the data size is incorrect. - Revamping
fidget-bytecode- Reserve register
u8::MAXto represent an inline immediate - Remove separate opcodes, e.g.
SubRegReg,SubRegImm, andSubImmRegnow all generateBytecodeOp::Sub(using the reserved register as needed for immediates). - This also removes the
enum RegOpDiscriminantsfromfidget_core
- Reserve register
- Add
PartialEqforGeometryPixel - Major refactoring of error types
fidget::Erroris removed in favor of fine-grained error types- The error type for a function now represents all of the errors that it can return (and no others)
- All errors still implement standard error traits, so application-level
code using
anyhow(or similar) may not need to change
- Fix a bug in bulk evaluator argument checks where mismatched slices could be allowed under some circumstances
- Change
depthmember inGeometryPixelfromu32tof32(#381) - Fixed memory corruption in a subset of x86 interval JIT functions, revealed
by changes in
rustc 1.93's codegen.
- Add
fidget-bytecodecrate (exported asfidget::bytecode), which is a binary representation of register instruction tapes. - Restore
fidget::shapesmodule, which was extracted tofidget-shapesbut accidentally not added to the top-level crate. - Add
Shape::<F, Transformed>::transform()to get the transform from a shape.
The focus of this release is splitting fidget into a set of smaller crates,
for improved compile times and modularity. This brings a clean build of
fidget (time cargo build --release -pfidget) down from 76 to 45 seconds on
my laptop, a significant improvement!
For the most part, this should be transparent: each smaller crate is re-exported
by the fidget root crate. However, a few things got moved around to improve
modularity:
- Image (bitmap and heightmap) rendering is moved from
fidget::renderto a separatefidget::rastermodule. Anything which was used by both image and mesh rendering remains infidget::render. View2andView3have moved tofidget::gui
The helper crates can be enabled with fine-grained features on the root fidget
crate, for use cases where a smaller fidget is helpful.
- Return
DoubleEndedIteratorfromRegTape::iter()andSsaTape::iter(), making it easier to iterate in reverse order. - Tighter interval bounds on
sinandcos(thanks @alexneufeld)
- Fix panic in tape construction if a multi-output expression has a constant as an output.
- Add
Function::can_simplifyto check whether aFunctioncan ever be simplified (if not, then interval evaluation isn't as useful). - Fix invalid access when doing bulk evaluation on a
Functionwith more than one output. This caused a panic using the VM evaluator, and a segfault (typically) using the JIT evaluator. - Make
PartialEqforTreeobjects do deep comparisons, instead of shallow (pointer) equality- This is more expensive, but matches typical data structures in Rust
Tree::ptr_eqcan be used to perform shallow equality checks
- Changed 3D image rendering to saturate when voxels are touching the camera.
Saturated voxels snap to the image's depth and have a normal of
[0, 0, 1]. - Replaced
viewinImageRenderConfig,VoxelRenderConfig, andmesh::Settingswith a genericworld_to_modelmatrix, for more flexibility when rendering. - Add mathematical constants for Rhai scripts (
PI,E,TAU...) - Add
std::ops::Negimplementation forVec2,3,4types, in both Rust and Rhai. - Remove
Shape::apply_transform; there were too many ways to apply transforms to shapes, and having mutable operations on a handle passed byCloneopens up possibilities for confusion. Here's the new plan:- When using high-level algorithms (e.g. meshing and rendering), the
world-to-model transform is now only set by the
world_to_modelmatrix in the config or settings object (ImageRenderConfig,VoxelRenderConfig,mesh::Settings) - When using a
Shapedirectly, a transform can be set withShape::with_transform. This returns a newShape<F, Transformed>, whereTransformedis a marker type indicating that the transform field isSome(..).Shape::with_transformis a one-way operation: it's only available onShape<F>(which is actually nowShape<F, ()>). Note that high-level operations require aShape<F, ()>, because they set the transform themselves!
- When using high-level algorithms (e.g. meshing and rendering), the
world-to-model transform is now only set by the
- Add
vec2([..])andvec3([..])functions to Rhai environment - Fix
remap_affinecomposition; previously, it applied operations in the wrong order given multiple affine transformations. - New shapes:
Rectangle,Box,Rotate(X|Y|Z),Blend,Plane,ScaleUniform,RevolveY,ExtrudeZ,LoftZ - Added ordered constructors to Rhai bindings, e.g.
rectangle([0,0], [1,1]). These are only available if a previous constructor didn't use the same function prototype. - Added
cancel: CancelTokentofidget::mesh::SettingsOctree::buildnow returns anOption<..>, which isNoneif it was cancelled
- Removed
SettingsfromOctree::walk_dual, because we aren't actually doing any multithreading - Switched various mesh functions to take
Settingsby reference instead of by value, becauseCancelTokenis notCopy. - Make Rhai
vec2(..)andvec3(..)functions idempotent, i.e.vec2(vec2(..))now works. - Optimize
atan2interval evaluation
- Bug fix:
Image::height()was returning width instead! - Add
#[derive(PartialEq)]toView2andView3 - Improve rendering at small images sizes
- Previously, we rendered at least one tile of size
cfg.tile_sizes[0] - Now, we pick the smallest possible tile size for the root tiles; if we're
rendering a 32×32 image with tile sizes of
[128, 32, 8], then we'll render a single 32×32 tile (instead of 128×128) - As part of this change, a few functions were removed from the
TileSizespublic API; they're now attached to an internalstruct TileSizesRef.
- Previously, we rendered at least one tile of size
- Add a new
trait ShapeVisitorandpub fn visit_shapes(..)for introspection into shapes and transforms defined in theshapesmodule. This is expected to be useful for automatically generated scripted bindings (indeed,fidget::rhainow uses it). - Added more document to
fidget::shapes - Made
fidget::rhai::shapes::register_shapeprivate - Significant changes to 2D rendering API:
- Changed
fidget::render::render2dto always return anImage<DistancePixel>, which encodes either a distance sample or details on fill at that point. - Removed the
trait RenderMode, which was previously used to generate pixel samples from a distance field. - Added
ImageRenderConfig::pixel_perfect, which forces sampling down to individual pixels. - Added
fidget::render::effects::{to_rgba_bitmap, to_debug_bitmap, to_rgba_distance}to post-process anImage<DistancePixel>. These replace the previousRenderModeobjects for generating specific flavors of image. - Note that approximate SDFs are entirely removed; they were not substantially faster, added complexity, and looked bad.
- Changed
- Add
#[facet(default = ...)]annotations to relevant fields infidget::shapes.- These annotations are used in the Rhai bindings to build shapes without
specifying every field, e.g.
circle(1)leaves the center as a default value.
- These annotations are used in the Rhai bindings to build shapes without
specifying every field, e.g.
- Fix issue where
Shape::eval_*functions would return an empty slice if there were no active variables in the shape; it now returns a slice that's the correct size (i.e. matching the input slices). - In Rhai bindings,
FromDynamicnow takes the field's default value as a hint, which may be used when promoting other types. For example, if you pass avec2(instead of avec3) to bothMoveandScale, thezcomponent will be set to 0 forMoveand 1 forScale. - Add helper functions to destructure and rebuild
Canvas2,Canvas3,View2, andView3into their component values. - Rename
fidget::{shapes, rhai}::vectofidget::{shapes, rhai}::types - Add more common types used in shapes and Rhai bindings:
Axisis a unitVec3Planeis anAxisand offset
- Add
Reflectshape, as well asReflectX/Y/Z - Removed
fidget::rhai::Engineandfidget::rhai::Axes- Previously,
fidget::rhai::Enginewrapped a Rhai engine and Fidget-specific functionality - The internals of
Enginewere tightly coupled tofidget-viewer, in ways that weren't useful for other GUIs. - Now,
fidget::rhai::engine()returns arhai::Engineobject with Fidget types pre-installed, but without the wrapper type. - The Rhai
draw(..)anddraw_rgb(..)functions are moved intofidget-viewer, because they're specific to that one GUI (fidget-cliis also aware ofdraw, but now has its own simpler implementation).
- Previously,
- Move
Vec2/3/4,Axis,Planeintofidget::shapes::typesinstead of re-exporting them infidget::shapes. - Make
fidget::shapes::types::{Value, Type}public; these represent types which can be used in shapes (with ergonomic Rhai bindings). - Fix missing local optimizations in
Context::import(e.g.x * 0 => 0) - Rename
remap_xyz(..)to justremap(..)in Rhai bindings; add a two-argument version which leaveszunchanged
- Small release to fix an issue with 0.3.6 being published with invalid local
changes (I thought
cargoprevented this, dunno how it happened) - Mark functions on
IntervalandGradas#[inline], to improve performance when those types are used outside of the Fidget crate itself. - Make
fidget::rhaisubmodules visible (vec,shape,tree) for finer-grained usage outside of Fidget. - Update to Rust 2024 edition, set minimum
rustcversion to 1.87 - Update dependencies; remove some that have become unused
- Change
Option<ThreadPool<'a>>toOption<&'a ThreadPool>throughout the codebase; moving the reference out of theThreadPooleliminates the need for a separaterayon::ThreadPoolobject on the stack. - Significant rewrite of meshing! It now uses the same
Option<&ThreadPool>type and is multithreaded using Rayon, meaning it can work in WebAssembly. - Changed 3D rendering and effects functions to use a new
GeometryBuffertype, which combines depth and normal data into a single image. - Add
fidget::guimodule, which definesCanvas2andCanvas3. The canvas types are stateful abstractions around a GUI canvas, with support for cursor interactions. - Change
ImageSize::transform_pointandVoxelSize::transform_pointto take a point withi32coordinates (instead off32). This helps us distinguish between screen (pixel) and world (floating-point) coordinates at the type level. - Add
Tree::remap_affine(andTreeOp::RemapAffine) to perform affine transformations on math expressions. These transformations are composable; two affine transforms will be combined into a single transform if stacked together. - Major updates to the Rhai standard library and default bindings:
- Add
vec2,vec3,vec4types - Shapes are now constructed with object maps
- Added documentation in
fidget::rhai::docsmodule
- Add
- Added
#[derive(Serialize, Deserialize)]toView2andView3 - Make
TranslateHandletake aconst N: usizeparameter - Use
TranslateHandleinView2(previously, it was only used inView3) - Make
translateandrotatefunctions borrow their respective handle, instead of taking it by value. - Fix docstring for
AndRegImm,AndRegReg,OrRegImm, andOrRegReg - Add
cancel: CancelTokento 2D and 3D rendering configuration objects; this is a sharedArc<AtomicBool>which can be used to stop rendering. The returned type is now anOption<...>, whereNoneindicates that rendering was cancelled. - Fix inconsistency between JIT and VM evaluator when performing interval
evaluation of
not([NAN, NAN]). - Propagate
NANvalues throughandandoroperations on intervals. - Add a new
Image<P>generic image type (wrapping aVec<P>,width, andheight).- Define
DepthImage,NormalImage, andColorImagespecializations - Use these types in 2D and 3D rendering
- Define
- Remove
Grad::to_rgbin favor of handling it at the image level - Add
fidget::render::effectsmodule for post-processing rendered images:- Combining depth and normal images into a shaded image
- Denoising normals to fix back-facing samples
- Computing and applying screen-space ambient occlusion
- Optimize implementation of interval
modulofor cases where the right-hand argument is a positive constant value (which is the most common when using it for domain repetition) - Update many dependencies to their latest versions
Due to getrandom#504,
crates which use Fidget as a library and compile to WebAssembly must
select a getrandom backend. This can be done either on the command line
(RUSTFLAGS='--cfg getrandom_backend="wasm_js"') or in a .cargo/config.toml
configuration file (e.g. this file
in Fidget itself).
See
the getrandom docs
for more details on why this is necessary.
- Add
GenericVmFunction::simplify_withto simultaneously simplify a function and pick a new register count for the resulting tape - Add bidirectional conversions between
JitFunctionandGenericVmFunction(exposing the inner data member) - Add a new
TileSizes(Vec<usize>)object representing tile sizes used during rendering. Unlike the previousVec<usize>, this data type checks our tile size invariants at construction. It is used in thestruct RenderConfig. - Rethink rendering and viewport configuration:
- Add a new
RegionSize<const N: usize>type (withImageSizeandVoxelSizealiases), representing a render region. This type is responsible for the screen-to-world transform - Add
View2andView3types, which stores the world-to-model transform (scaling, panning, etc) - Image rendering uses both
RegionSizeandViewX; this means that we can now render non-square images! - Meshing uses just a
View3, to position the model within the ±1 bounds - The previous
fidget::shape::Boundstype is removed - Remove
fidget::render::render2d/3dfrom the public API, as they're equivalent to the functions onImageRenderConfig/VoxelRenderConfig
- Add a new
- Move
RenderHintsintofidget::render - Remove fine-grained features from
fidgetcrate, because we aren't actually testing the power-set of feature combinations in CI (and some were breaking!). The only remaining features arerhai,jitandeval-tests. - Add new
ShapeVars<T>type, representing a map fromVarIndex -> T. This type is used for high-level rendering and meshing ofShapeobjects that include supplementary variables - Add
Octree::build_with_varsandImage/VoxelRenderConfig::run_with_varsfunctions for shapes with supplementary variables - Change
ShapeBulkEval::eval_vto take single variables (i.e.x,y,zvary but each variable has a constant value across the whole slice). AddShapeBulkEval::eval_vsifx,y,zand variables are all changing through the slices. - Add a new
GenericVmTape<N>type, and use it for VM evaluation. Previously, theGenericVmFunction<N>type implemented bothTapeandFunction. - Add
vars()toFunctiontrait, because there are cases where we want to get the variable map without building a tape (and it must always be the same). - Fix soundness bug in
Mmap(probably not user-visible) - Add
Send + Sync + Clonebounds to thetrait Tape, to make them easily shared between threads. Previously, we used anArc<Tape>to share tapes between threads, but tapes were already using anArc<..>under the hood. - Changed
Tape::recyclefrom returning aStorageto returning anOption<Storage>, as tapes may now be shared between threads. - Use Rayon for 2D and 3D rasterization
- The
threadsmember ofVoxelRenderConfigandImageRenderConfigis now aOption<ThreadPool>, which can beNone(use a single thread),Some(ThreadPool::Global)(use the global Rayon pool), orSome(ThreadPool::Custom(..))(use a user-provided pool) - This is a step towards WebAssembly multithreading, using
wasm-bindgen-rayon. ThreadCountis moved tofidget::mesh, because that's the only place it's now used- The plan is to switch to Rayon for meshing as well, eventually
- The
- Tweak
View2andView3APIs to make them more useful as camera types
Functionand evaluator types now produce multiple outputsMathFunction::newnow takes a slice of nodes, instead of a single node- All of the intermediate tape formats (
SsaTape, etc) are aware of multiple output nodes - Evaluation now returns a slice of outputs, one for each root node (ordered
based on order in the
&[Node]slice passed toMathFunction::new)
RegisterAllocatorno longer binds SSA register 0 to physical register 0 by default. If you don't know what this means, don't worry about it.
- Added
impl IntoNode for Var, to make handlingVarvalues in a context easier. - Added
impl From<TreeOp> for Treefor convenience - Added
Context::export(&self, n: Node) -> Treeto make a freestandingTreegiven a context-specificNode. - Fix possible corruption of
x24during AArch64 float slice JIT evaluation, due to incorrect stack alignment. - Added
Context::derivandTree::derivto do symbolic differentiation of math expressions.
The highlight of this release is the fidget::solver module, which implements
the Levenberg-Marquardt algorithm to minimize a system of equations (represented
as fidget::eval::Function objects). It's our first official case of using
Fidget's types and traits for things other than pure implicit surfaces!
- Fixed a bug in the x86 JIT which could corrupt registers during gradient
(
grad_slice) evaluation - Renamed
Context::const_valuetoContext::get_constand tweaked its return type to matchContext::get_var. - Added
impl From<i32> for Treeto make writing tree expressions easier - Removed
Error::ReservedNameandError::DuplicateName, which were unused - Add the
fidget::solvermodule, which contains a simple solver for systems of equations. The solver requires the equations to implement Fidget'sFunctiontrait. It uses both point-wise and gradient evaluation to solve for a set ofVarvalues, using the Levenberg-Marquardt algorithm. - Add
Tree::var()andimpl TryFrom<Tree> for Var
- Major refactoring of core evaluation traits
- The lowest-level "thing that can be evaluated" trait has changed from
Shape(taking(x, y, z)inputs) toFunction(taking an arbitrary number of variables). Shapeis now a wrapper around aF: Functioninstead of a trait.- Shape evaluators are now wrappers around
E: BulkEvaluatororE: TracingEvaluator, which convert(x, y, z)arguments into list-of-variables arguments. - Using the
VmShapeorJitShapetypes should be mostly the same as before; changes are most noticeable if you're writing things that are generic acrossS: Shape.
- The lowest-level "thing that can be evaluated" trait has changed from
- Major refactoring of how variables are handled
- Removed
VarNode; the canonical variable type isVar, which is its own unique index. - Removed named variables, to make
VartriviallyCopy + Clone. - Added
vars()method toFunctiontrait, allowing users to look up the mapping from variable to evaluation index. AVarnow represents a persistent identity fromTreetoContexttoFunctionevaluation. - Move
VarandVarMapintofidget::varsmodule, because they're no longer specific to aContext. Op::Inputnow takes au32argument, instead of au8
- Removed
- Fixed a bug in the AArch64 JIT would which could corrupt certain registers during interval evaluation.
This release brings us to opcode parity with libfive's operators, adding
atan2 and various rounding operations. In addition, there are a few new APIs
and rearrangements in preparation for larger refactoring to come.
- Changed to 2D rendering API to support render modes which use linear
interpolation to process full / empty regions
- Specifically,
RenderMode::intervalnow returns anIntervalAction, which can beFill(..),Recurse, orInterpolate. - Modify
SdfRenderModeuse this interpolation; the previous pixel-perfect behavior is renamed toSdfPixelRenderModel - Make
RenderModetrait methods static, because they weren't using&self - Change signature of
fidget::render::render2dto pass the mode only as a generic parameter, instead of an argument
- Specifically,
- Add new operations:
floor,ceil,round,atan2 - Changed
BulkEvaluator::evalsignature to take x, y, z arguments as&[T]instead of&[f32]. This is more flexible for gradient evaluation, because it allows the caller to specify up to three gradients, without pinning them to specific argument. - Moved
tile_sizes_2d,tile_sizes_3d, andsimplify_tree_during_meshinginto a newfidget::shape::RenderHitstrait. This is a building block for generic (n-variable) function evaluation.
This is a relatively small release; there are a few features to improve the
WebAssembly demo, bug fixes and improvements for very deep Tree objects, and
one more public API.
- Added
VmShapeserialization (usingserde), specifically#[derive(Serialize, Deserialize)}onVmDataimpl From<VmData<255>> for VmShape { .. }
- Fixed stack overflows when handling very deep
Treeobjects- Added a non-recursive
Dropimplementation - Rewrote
Context::importto use the heap instead of stack
- Added a non-recursive
- Updated
Context::importto cache theTreeOp → Nodemapping, which is a minor optimization (probably only relevant for unreasonably largeTreeobjects) - Made
fidget::render::RenderHandlepublic and documented
The highlight of this release is native Windows support (including JIT compilation). Everything should work out of the box; please open an issue if things are misbehaving.
There are a handful of API and feature changes as well:
- Removed
threadsparameter from variousSettingsobjects when targetingwasm32, so that it's harder to accidentally spawn threads (which would panic). - Removed
min_depth/max_depthdistinction in meshing; this doesn't actually guarantee manifold models (see this scale-invariant adversarial model), so the extra complexity isn't worth it. - Added Windows support (including JIT)
- Removed
write-xor-executefeature, which was extra cognitive load that no one had actually asked for; if you care about it, let me know!
The highlight of this release is a refactoring of how shapes are handled in Rhai
scripts: instead of being built from thunks (unevaluated closures) and evaluated
in draw(..) and draw_rgb(..), they are built directly using a new Tree
object. This makes writing scripts more ergonomic:
// Before
fn circle(cx, cy, r) {
|x, y| {
sqrt((x - cx) * (x - cx) +
(y - cy) * (y - cy)) - r
}
}
// After
fn circle(cx, cy, r) {
let ax = axes();
sqrt((ax.x - cx) * (ax.x - cx) +
(ax.y - cy) * (ax.y - cy)) - r
}The change is even more noticeable on higher-level functions (functions which operate on shapes):
// Before
fn intersection(a, b) {
|x, y| {
max(a.call(x, y), b.call(x, y))
}
}
// After
fn intersection(a, b) {
a.max(b)
}- Add helper function
Context::if_nonzero_elseto build conditionals (using the logical operators added in version 0.2.3) - Add
fidget::context::{Tree, TreeOp}. These types allow construction of math trees without a parentContext(and therefore without deduplication); the resulting trees can be loaded into aContextusingContext::import. This replaces theBoundNodeandBoundContexttypes (previously only available for unit tests). - Remove
Context::remap_xyzin favor of lazy remapping withTree::remap_xyz. - Use
Treein Rhai scripts, removing the per-scriptContext. This is an API change: previously, shapes in Rhai were thunks evaluated during calls todraw(..); now, shapes in Rhai areTreeobjects, and there's much less function wrangling required. - Remove the distinction between variables and inputs (by removing all
Var-related functions and opcodes). This was over-engineered; the plan going forward will be to support functions with N inputs (currently fixed to 3,x/y/z). - Improved robustness of
viewerapplication when editors move files instead of writing to them directly. - Add Rhai bindings for all new opcodes
- Tweak JIT calling convention to take an array of inputs instead of X, Y, Z arguments.
- Fix a possible panic during multithreaded 3D rendering of very small images
- Add
compareoperator (equivalent to<=>in C++ orpartial_cmpin Rust, with the difference that unordered results are returned asNAN) - Fix a bug in the x86 JIT evaluator's implementation of interval
abs - Add generic
TransformedShape<S>, representing a shape transformed by a 4x4 homogeneous matrix. This replacesRenderConfig::matas the flexible strategy for rotation / scale / translation / perspective transforms, e.g. for interactive visualization (where you don't want to remap the underlying shape) - Introduce a new
Boundstype, representing an X/Y/Z region of interest for rendering or meshing. This overlaps somewhat withTransformedShape, but it's ergonomic to specify render region instead of having to do the matrix math every time.- Replaced
RenderConfig::matwith a newboundsmember. - Added a new
boundsmember tomesh::Settings, for octree construction
- Replaced
- Move
IntervalandGradtofidget::typesmodule, instead offidget::eval::types. - Fix an edge case in meshing where nearly-planar surfaces could produce vertices far from the desired position.
- Add the
modulo(Euclidean remainder) operation - Add logical operations (
and,or,not), which can be used to build pseudo-conditionals which are simplified by theTracingEvaluator.
- Added many transcendental functions:
sin,cos,tan,asin,acos,atan,exp,ln - Implemented more rigorous testing of evaluators, fixed a bunch of edge cases
(mostly differences in
NANhandling between platforms) - Tweaks to register allocator, for a small performance improvement
- Changed
fidget::eval::Varsto borrow instead of use anArc - Properly pre-allocated
mmapregions based on estimated size - Bump dependencies, fixing a Dependabot warning about
attybeing unmaintained - Fixed performance regressions in 3D rendering
- Added
wasm32-unknown-unknowntarget to CI checks - Reordered load/store generation so that register allocation generates linear code ("linear" in the type-system sense).
- Small optimizations to register allocation
- Added unit tests for rendering correctness
This release includes a significant amount of reorganization and refactoring. There are two main sets of changes, along with some minor tweaks.
Previously, evaluation was tightly coupled to the Tape type, and individual
evaluators were tightly bound to a specific tape. Upon reflection, both of
these decisions were reconsidered:
- We may want to use Fidget's algorithms on implicit models that aren't defined as tapes, so the evaluation traits should be more generic
- The previous code put a great deal of effort into reusing evaluator workspaces, but it was hard; it turns out that it's much cleaner to have evaluators own their workspaces but not own their tapes
These changes can be seen in the new fidget::eval::Shape trait, which replaces
(but is not equivalent to) the previous fidget::eval::Family trait.
Compiler modules are reorganized to live under fidget::compiler instead of
being split between fidget::ssa and fidget::vm.
- Improve meshing quality (better vertex placement, etc)
- Add parallelism to meshing implementation, configured by the new
fidget::mesh::Settings. - Added documentation to
fidget::meshmodules
- Added support for
aarch64-unknown-linux-*to the JIT compiler; previously,aarch64was only supported on macOS. - Added initial meshing support in the
fidget::meshmodule, gated by themeshfeature (enabled by default).
- Added
x86_64backend for the JIT compiler