From e1a50b8e42e9330f2ff0f9161eb25808ac7dd543 Mon Sep 17 00:00:00 2001 From: Not Zed Date: Tue, 5 Apr 2022 09:50:28 +0930 Subject: [PATCH] Flesh out vkheader implementation, uses registry and custom callbacks to fill out binding. Move vkregistry examples to separate module Comment out broken Frame implementation for now, just uses locally scoped arena allocator. --- Makefile | 14 +- src/notzed.clstatic/gen/opencl.api | 2 +- src/notzed.nativez/bin/generate-api | 93 +-- .../classes/au/notzed/nativez/ByteArray.java | 4 + .../classes/au/notzed/nativez/Frame.java | 135 ++-- .../classes/au/notzed/nativez/Memory.java | 12 + src/notzed.nativez/lib/api.pm | 166 +++-- src/notzed.nativez/lib/code.api | 36 + src/notzed.nativez/lib/code.pm | 40 +- src/notzed.nativez/lib/config.pm | 4 +- src/notzed.nativez/lib/method.pm | 5 +- src/notzed.nativez/lib/types.api | 43 +- .../classes/module-info.java | 6 + .../classes/vulkan/test/TestMandelbrot.java} | 101 ++- .../gen/cube.frag | 0 .../gen/cube.vert | 0 src/notzed.vkheader.test/gen/gen.make | 18 + .../gen/mandelbrot.comp | 0 src/notzed.vkheader/classes/module-info.java | 2 +- src/notzed.vkheader/gen/gen.make | 1 - src/notzed.vkheader/gen/vkheader.api | 279 +++++++- src/notzed.vkheader/gen/vkheader.pm | 674 +++++++++++++++++- src/notzed.vkheader/gen/vulkan.pm | 352 +++++++++ .../classes/module-info.java | 7 + .../classes/vulkan/test/Cube.java | 0 .../classes/vulkan/test/GLMaths.java | 0 .../classes/vulkan/test/TestCube.java | 0 .../classes/vulkan/test/TestMandelbrot.java | 0 src/notzed.vkregistry.test/gen/cube.frag | 8 + src/notzed.vkregistry.test/gen/cube.vert | 14 + src/notzed.vkregistry.test/gen/gen.make | 17 + .../gen/mandelbrot.comp | 60 ++ .../classes/module-info.java | 3 - src/notzed.vkregistry/gen/gen.make | 23 +- 34 files changed, 1834 insertions(+), 285 deletions(-) create mode 100644 src/notzed.vkheader.test/classes/module-info.java rename src/{notzed.vkheader/classes/vulkan/test/TestVulkan.java => notzed.vkheader.test/classes/vulkan/test/TestMandelbrot.java} (87%) rename src/{notzed.vkregistry => notzed.vkheader.test}/gen/cube.frag (100%) rename src/{notzed.vkregistry => notzed.vkheader.test}/gen/cube.vert (100%) create mode 100644 src/notzed.vkheader.test/gen/gen.make rename src/{notzed.vkregistry => notzed.vkheader.test}/gen/mandelbrot.comp (100%) create mode 100644 src/notzed.vkheader/gen/vulkan.pm create mode 100644 src/notzed.vkregistry.test/classes/module-info.java rename src/{notzed.vkregistry => notzed.vkregistry.test}/classes/vulkan/test/Cube.java (100%) rename src/{notzed.vkregistry => notzed.vkregistry.test}/classes/vulkan/test/GLMaths.java (100%) rename src/{notzed.vkregistry => notzed.vkregistry.test}/classes/vulkan/test/TestCube.java (100%) rename src/{notzed.vkregistry => notzed.vkregistry.test}/classes/vulkan/test/TestMandelbrot.java (100%) create mode 100644 src/notzed.vkregistry.test/gen/cube.frag create mode 100644 src/notzed.vkregistry.test/gen/cube.vert create mode 100644 src/notzed.vkregistry.test/gen/gen.make create mode 100644 src/notzed.vkregistry.test/gen/mandelbrot.comp diff --git a/Makefile b/Makefile index fafe48f..1bc01ce 100644 --- a/Makefile +++ b/Makefile @@ -9,24 +9,30 @@ java_MODULES = notzed.nativez \ notzed.apistatic notzed.apiobject \ notzed.ffmpeg \ notzed.clstatic \ - notzed.xlib notzed.vkregistry + notzed.xlib \ + notzed.vkregistry notzed.vkregistry.test \ + notzed.vkheader notzed.vkheader.test native_MODULES = notzed.api notzed.apistatic_JDEPMOD = notzed.nativez notzed.api notzed.apiobject_JDEPMOD = notzed.nativez notzed.api notzed.ffmpeg_JDEPMOD = notzed.nativez notzed.clstatic_JDEPMOD = notzed.nativez -notzed.vkregistry_JDEPMOD = notzed.nativez notzed.xlib +notzed.vkregistry_JDEPMOD = notzed.nativez +notzed.vkregistry.test_JDEPMOD = notzed.vkregistry notzed.xlib notzed.vkheader_JDEPMOD = notzed.nativez +notzed.vkheader.test_JDEPMOD = notzed.vkheader notzed.apistatic_JMAIN = api.test.TestAPI notzed.apiobject_JMAIN = api.test.TestAPI notzed.ffmpeg_JMAIN = ffmpeg.test.TestFFMPEG notzed.clstatic_JMAIN = opencl.test.clinfo -notzed.vkregistry_JMAIN = vulkan.test.TestMandelbrot vulkan.test.TestCube +notzed.vkregistry.test_JMAIN = vulkan.test.TestMandelbrot vulkan.test.TestCube +notzed.vkheader.test_JMAIN = vulkan.test.TestMandelbrot $(foreach module,$(java_MODULES),$(eval $(module)_JMAINFLAGS=--enable-native-access=notzed.nativez,$(module))) -notzed.vkregistry_JMAINFLAGS = --enable-native-access=notzed.nativez,notzed.xlib,notzed.vkregistry +notzed.vkregistry.test_JMAINFLAGS = --enable-native-access=notzed.nativez,notzed.xlib,notzed.vkregistry +notzed.vkheader.test_JMAINFLAGS = --enable-native-access=notzed.nativez,notzed.vkheader include java.make diff --git a/src/notzed.clstatic/gen/opencl.api b/src/notzed.clstatic/gen/opencl.api index 3bad336..e06add3 100644 --- a/src/notzed.clstatic/gen/opencl.api +++ b/src/notzed.clstatic/gen/opencl.api @@ -155,7 +155,7 @@ library cl_khr_gl_event success:errcode_ret=CL.CL_SUCCESS { code: {{ - public {name} create(cl_platform_id platform, ResourceScope scope) { + public cl_khr_gl_event create(cl_platform_id platform, ResourceScope scope) { // OpenCL 1.2+ only return create((s) -> CL.clGetExtensionFunctionAddressForPlatform(platform, s), scope); //return create(CL::clGetExtensionFunctionAddress, scope); diff --git a/src/notzed.nativez/bin/generate-api b/src/notzed.nativez/bin/generate-api index f0fee75..9f35fa4 100755 --- a/src/notzed.nativez/bin/generate-api +++ b/src/notzed.nativez/bin/generate-api @@ -1,5 +1,7 @@ #!/usr/bin/perl +# TODO: formatLibrary - code: has no template applied, no vars available to do so + # usage # -t package target package # -d directory output root @@ -21,7 +23,9 @@ use api; use code; use method; -$SIG{ __DIE__ } = sub { Carp::confess( @_ ) }; +$SIG{__DIE__} = sub { Carp::confess( @_ ) }; +$SIG{'INT'} = sub { Carp::confess() }; + $Data::Dumper::Indent = 1; my $apidef = "api.api"; @@ -72,6 +76,7 @@ exit 0; sub formatFunction { my $api = shift; my $c = shift; + my $tname = shift; my $template = shift; my $info = new method($api, $c); @@ -81,14 +86,23 @@ sub formatFunction { my $code; - #foreach my $l (split /\n/,Dumper($info->{vars}).Dumper($info->{arguments})) { - # $code .= "// $l\n"; - #} + # allow per-function override + $template = $api->findTemplateName($c->{$tname}) if defined $c->{$tname}; + + if (defined($template)) { + if ($tname eq 'func:template') { + foreach my $l (split /\n/,Dumper($c)) { + $code .= "// $l\n"; + } + } - $code .= code::applyTemplate($template, $info->{vars}); - $code =~ s/^\s*\n//osgm; + $code .= code::applyTemplate($template, $info->{vars}); + $code =~ s/^\s*\n//osgm; - return $code; + return $code; + } else { + return (); + } } sub formatCall { @@ -125,21 +139,28 @@ sub formatItems { $res->{seen}->{"$_->{type}:$_->{name}"}++ == 0 } $api->findMatches($inc, $res->{ctx}); - if ($inc->{type} eq 'func') { + if ($obj->{name} eq 'VkInstance') { + print Dumper($obj, $inc, \@list); + die; + } + + if ($inc->{type} eq 'func' && $inc->{literal}) { + push @{$res->{func}}, $inc->{literal}; + } elsif ($inc->{type} eq 'func') { my $def = $api->{index}->{'func:'}; - my $func = api::optionValue('func:template', 'code:method=invoke', $inc, $res->{template}); - my $init = api::optionValue('init:template', undef, $inc, $res->{template}); - my $funct = findTemplateName($api, $func); - my $initt = findTemplateName($api, $init) if defined $init; + my $func = api::optionValue('func:template', 'code:method=invoke', $inc, $res->{template}, $def); + my $init = api::optionValue('init:template', undef, $inc, $res->{template}, $def); + my $funct = $api->findTemplateName($func); + my $initt = $api->findTemplateName($init) if defined $init; - push @{$res->{func}}, map { formatFunction($api, $_, $funct) } @list; - push @{$res->{init}}, map { formatFunction($api, $_, $initt) } @list if defined($initt); + push @{$res->{func}}, map { formatFunction($api, $_, 'func:template', $funct) } @list; + push @{$res->{init}}, map { formatFunction($api, $_, 'init:template', $initt) } @list; } elsif ($inc->{type} eq 'define') { push @{$res->{define}}, map { code::formatDefine($api, $_) } @list; } elsif ($inc->{type} eq 'enum') { - push @{$res->{enum}}, map { code::formatEnum($api, $_) } @list; + push @{$res->{enum}}, map { code::formatEnum($api, $_, $res->{seen}) } @list; } elsif ($inc->{type} eq 'call') { - push @{$res->{enum}}, map { formatCall($api, $_) } @list; + push @{$res->{call}}, map { formatCall($api, $_) } @list; } else { die; } @@ -180,27 +201,6 @@ sub formatLibrary { } } -sub findTemplateName { - my $api = shift; - my $name = shift; - - if ($name =~ m/^(.+)=(.+)$/) { - my $template = api::findItem($api->{index}->{$1}, $2); - return $template if defined $template; - } - die "can't find template '$name'\n"; -} - -sub findTemplate { - my $api = shift; - my $obj = shift; - my $s = shift; - my $data = $api->{data}; - my $def = $api->{index}->{"$s->{type}:"}; - my $name = api::optionValue('template', $s->{size} == 0 ? 'code:class=handle' : 'code:class=struct', $obj, $def); - - return findTemplateName($api, $name); -} # TODO: embedded structs sub formatStruct { @@ -209,7 +209,7 @@ sub formatStruct { my $s = shift; my $data = $api->{data}; my $seen = {}; - my $structTemplate = findTemplate($api, $obj, $s); + my $structTemplate = $api->findTemplate($obj, $s); my @members = code::scanFields($api, $s); my @membersOutput = grep { $_->{field}->{output} } @members; @@ -238,6 +238,7 @@ sub formatStruct { ctx => $s, library => [], func => [], + init => [], define => [], enum => [], call => [], @@ -247,6 +248,11 @@ sub formatStruct { formatLibrary($api, $obj, $res); + # handle per-struct format functions + foreach my $fmt (@{$s->{postformat}}) { + $fmt->($api, $obj, $res, $s); + } + my $vars = { %{$api->{vars}}, layout => code::formatStructLayout($api, $s, \@members), @@ -254,19 +260,20 @@ sub formatStruct { name => $s->{name}, varhandles => $varhandles, accessors => $accessors, + init => join("\n", @{$res->{init}}), methods => join("\n", @{$res->{func}}), defines => join("\n", @{$res->{define}}), enums => join("\n", @{$res->{enum}}), }; my $code; - foreach my $l (split /\n/,Dumper($s, \@members)) { + foreach my $l (split /\n/,Dumper({ template => $api->queryTemplate($obj, $s), struct => $s }, \@members)) { $code .= "//$l\n"; } $api->{output}->{"$s->{type}:$s->{name}"} = 1; - return $code.code::applyTemplate($structTemplate, $vars); + return $code.code::applyTemplate($structTemplate, $vars, 1); } # output all libraries @@ -279,7 +286,7 @@ sub exportLibraries { next if $api->{output}->{"$obj->{type}:$obj->{name}"}; next if $obj->{output} != 1; - my $library = findTemplateName($api, api::optionValue('template', 'code:class=library', $obj, $def)); + my $library = $api->findTemplateName(api::optionValue('template', 'code:class=library', $obj, $def)); my $res = { ctx => $obj, library => [], @@ -304,7 +311,7 @@ sub exportLibraries { calls => join("\n", @{$res->{call}}), }; - export($api, $obj->{name}, code::applyTemplate($library, $vars)); + export($api, $obj->{name}, code::applyTemplate($library, $vars, 1)); } } @@ -343,6 +350,7 @@ sub exportStructs { print "gen ".($#list+1)." $obj->{type} $obj->{name}\n" if ($api->{vars}->{verbose} > 0); foreach my $s (@list) { next if $api->{output}->{"$s->{type}:$s->{name}"}; + print " $s->{name}\n" if ($api->{vars}->{verbose} > 1); export($api, $s->{rename}, formatClass($api, $obj, $s)); @@ -352,6 +360,7 @@ sub exportStructs { # then anything else left using the default outputs foreach my $s (grep { $_->{output} && ($_->{type} =~ m/struct|union|call/) && !$api->{output}->{"$_->{type}:$_->{name}"} } @{$api->{api}}) { my $obj = $data->{"$s->{type}:"}; + print "* $s->{name}\n" if ($api->{vars}->{verbose} > 1); export($api, $s->{rename}, formatClass($api, $obj, $s)); } } diff --git a/src/notzed.nativez/classes/au/notzed/nativez/ByteArray.java b/src/notzed.nativez/classes/au/notzed/nativez/ByteArray.java index bfe21da..0abf605 100644 --- a/src/notzed.nativez/classes/au/notzed/nativez/ByteArray.java +++ b/src/notzed.nativez/classes/au/notzed/nativez/ByteArray.java @@ -91,4 +91,8 @@ public class ByteArray extends AbstractList implements Pointer { public void setAtIndex(long index, byte value) { segment.set(Memory.BYTE, index, value); } + + public String toUtf8String() { + return segment.getUtf8String(0L); + } } diff --git a/src/notzed.nativez/classes/au/notzed/nativez/Frame.java b/src/notzed.nativez/classes/au/notzed/nativez/Frame.java index 560ba5a..a694770 100644 --- a/src/notzed.nativez/classes/au/notzed/nativez/Frame.java +++ b/src/notzed.nativez/classes/au/notzed/nativez/Frame.java @@ -34,62 +34,84 @@ import static jdk.incubator.foreign.ValueLayout.OfAddress; */ public class Frame implements AutoCloseable, SegmentAllocator { - private final long tos; - private Stack stack; - private ResourceScope overflow; + private final ResourceScope scope; + private final SegmentAllocator alloc; - Frame(long tos, Stack stack) { - this.tos = tos; - this.stack = stack; + Frame() { + this.scope = ResourceScope.newConfinedScope(); + this.alloc = SegmentAllocator.newNativeArena(scope); } - private static final ResourceScope scope = ResourceScope.newSharedScope(Cleaner.create()); - private static final ThreadLocal stacks = ThreadLocal.withInitial(() -> new Stack(scope)); - public static Frame frame() { - return stacks.get().createFrame(); - } - - private static class Stack { - private final MemorySegment stack; - private long sp; - private Thread thread = Thread.currentThread(); - - Stack(ResourceScope scope) { - stack = MemorySegment.allocateNative(4096, 4096, scope); - sp = 4096; - } - Frame createFrame() { - return new Frame(sp, this); - } + return new Frame(); } @Override public MemorySegment allocate(long size, long alignment) { - if (stack.thread != Thread.currentThread()) - throw new IllegalStateException(); - if (alignment != Long.highestOneBit(alignment)) - throw new IllegalArgumentException(); - if (stack.sp >= size) { - stack.sp = (stack.sp - size) & ~(alignment - 1); - return stack.stack.asSlice(stack.sp, size).fill((byte)0); - } else { - // or arena allocator? - if (overflow == null) - overflow = ResourceScope.newConfinedScope(); - return MemorySegment.allocateNative(size, alignment, overflow); - } + return alloc.allocate(size, alignment); } @Override public void close() { - stack.sp = tos; - stack = null; - if (overflow != null) { - overflow.close(); - overflow = null; - } - } + scope.close(); + } + + // private final long tos; + // private Stack stack; + // private ResourceScope overflow; + + // Frame(long tos, Stack stack) { + // this.tos = tos; + // this.stack = stack; + // } + + // private static final ResourceScope scope = ResourceScope.newSharedScope(Cleaner.create()); + // private static final ThreadLocal stacks = ThreadLocal.withInitial(() -> new Stack(scope)); + + // public static Frame frame() { + // //return stacks.get().createFrame(); + // } + + // private static class Stack { + // private final MemorySegment stack; + // private long sp; + // private Thread thread = Thread.currentThread(); + + // Stack(ResourceScope scope) { + // stack = MemorySegment.allocateNative(4096, 4096, scope); + // sp = 4096; + // } + // Frame createFrame() { + // return new Frame(sp, this); + // } + // } + + // @Override + // public MemorySegment allocate(long size, long alignment) { + // if (stack.thread != Thread.currentThread()) + // throw new IllegalStateException(); + // if (alignment != Long.highestOneBit(alignment)) + // throw new IllegalArgumentException(); + // if (stack.sp >= size) { + // stack.sp = (stack.sp - size) & ~(alignment - 1); + // return stack.stack.asSlice(stack.sp, size).fill((byte)0); + // } else { + // // or arena allocator? + // if (overflow == null) + // overflow = ResourceScope.newConfinedScope(); + // return MemorySegment.allocateNative(size, alignment, overflow); + // } + // } + + // @Override + // public void close() { + // stack.sp = tos; + // stack = null; + // if (overflow != null) { + // overflow.close(); + // overflow = null; + // } + // } public MemorySegment allocateInt() { return allocate(Memory.INT); @@ -196,4 +218,29 @@ public class Frame implements AutoCloseable, SegmentAllocator { return Memory.NULL; } } + + public static MemorySegment copy(MemorySegment ctx, String string) { + if (string != null) { + SegmentAllocator alloc = SegmentAllocator.nativeAllocator(ctx.scope()); + + return alloc.allocateUtf8String(string); + } else { + return Memory.NULL; + } + } + + public static MemorySegment copy(MemorySegment ctx, String[] array) { + System.out.printf("copy array %s\n", array != null ? array.length : ""); + if (array != null) { + SegmentAllocator alloc = SegmentAllocator.nativeAllocator(ctx.scope()); + MemorySegment list = alloc.allocateArray(Memory.POINTER, array.length); + for (int i = 0; i < array.length; i++) { + System.out.printf(" [%d] '%s'\n", i, array[i]); + list.setAtIndex(Memory.POINTER, i, alloc.allocateUtf8String(array[i])); + } + return list; + } else { + return Memory.NULL; + } + } } diff --git a/src/notzed.nativez/classes/au/notzed/nativez/Memory.java b/src/notzed.nativez/classes/au/notzed/nativez/Memory.java index c935f22..1484708 100644 --- a/src/notzed.nativez/classes/au/notzed/nativez/Memory.java +++ b/src/notzed.nativez/classes/au/notzed/nativez/Memory.java @@ -45,6 +45,10 @@ public class Memory { return sharedScope; } + public static MethodHandle downcall(FunctionDescriptor desc) { + return CLinker.systemCLinker().downcallHandle(desc); + } + public static MethodHandle downcall(String name, FunctionDescriptor desc) { return SymbolLookup.loaderLookup().lookup(name) .map(sym -> CLinker.systemCLinker().downcallHandle(sym, desc)) @@ -122,6 +126,14 @@ public class Memory { return list != null ? list.length() : 0; } + public static long length(MemorySegment list) { + return list != null ? list.byteSize() : 0; + } + + public static long length(Object[] list) { + return list != null ? list.length : 0; + } + public static long size(MemorySegment s) { return s != null ? s.byteSize() : 0; } diff --git a/src/notzed.nativez/lib/api.pm b/src/notzed.nativez/lib/api.pm index 636bb41..c758616 100644 --- a/src/notzed.nativez/lib/api.pm +++ b/src/notzed.nativez/lib/api.pm @@ -1,4 +1,7 @@ +# TODO: define more & consistent stage processing hooks, currently implicit require init() and stage.postprocess +# TODO: code: etc should probably instead be code: where is one of the template fields - methods, init, etc. + package api; use strict; @@ -7,6 +10,7 @@ use File::Path qw(make_path); use File::Basename; use Data::Dumper; use List::Util qw(first); +use Carp 'verbose'; use config; @@ -55,7 +59,8 @@ my %defaultTable = ( items => [], options => [], regex => qr/^func:$/, - type => 'func' + type => 'func', + 'func:template' => 'code:method=invoke' }, 'enum:' => { name => '', @@ -96,9 +101,27 @@ sub new { $self->{data} = { %{$self->{data}}, %{$info} }; } + # FIXME: fix the list names in export.cc and export-defines instead + # set all the member list names to be consistent + foreach my $s (values %{$self->{data}}) { + foreach my $n ('fields', 'arguments', 'values') { + if ($s->{$n}) { + $s->{items} = $s->{$n}; + delete $s->{$n}; + } + } + } + + bless $self, $class; + + # handle requires and init modules foreach my $p (@{$conf->{pragmas}}) { if ($p->[0] eq '%require') { require $p->[1]; + my $n = $p->[1]; + $n =~ s/.pm$/::init(\$self)/; + print "$n\n"; + eval $n || die "module init failed: $! $@"; } } @@ -127,6 +150,12 @@ sub new { postprocess($self); + foreach my $p (@{$conf->{pragmas}}) { + if ($p->[0] eq '%stage.postprocess') { + eval "$p->[1](\$self)" || die "$p->[0] $p->[1] failed: $! $@"; + } + } + # form the types # TODO: could match the regexes to every possible type in the api and create a direct index my $copyIndex = {}; @@ -138,8 +167,6 @@ sub new { push @{$self->{types}}, initType($self, $copyIndex, $obj); } - bless $self, $class; - return $self; } @@ -173,7 +200,7 @@ sub openOutput { make_path($dir) if (!-d $dir); open(my $f, ">", $path.'~') || die ("Cannot open '$path' for writing"); - print "writing '$path'\n"; + #print "writing '$path'\n"; $f; } @@ -190,9 +217,10 @@ sub initType { my $type = {}; $type->{options} = [ @{$obj->{options}} ]; + $type->{file} = $obj->{file}; # FIXME: per-item options, set etc? - foreach my $inc (@{$obj->{items}}) { + foreach my $inc (grep { defined $_->{literal} } @{$obj->{items}}) { $type->{items}->{$inc->{match}} = $inc->{literal}; } @@ -231,7 +259,7 @@ sub findType { my @list; foreach my $type (@{$api->{types}}) { - my $select = !defined($type->{select}) || defined($m->{$type->{select}}); + my $select = !defined($type->{select}) || defined($m->{select}->{$type->{select}}); if ($select && ($deref =~ m/$type->{regex}/)) { my %match = %+; @@ -243,6 +271,41 @@ sub findType { undef; } +sub findTemplateName { + my $api = shift; + my $name = shift; + + if ($name =~ m/^(.+)=(.+)$/) { + my $template = findItem($api->{index}->{$1}, $2); + return $template if defined $template; + } + die "can't find template '$name'\n"; +} + +sub queryTemplate { + my $api = shift; + my $obj = shift; + my $s = shift; + my $sname = $s->{"$s->{type}:template"}; + my $def = $api->{index}->{"$s->{type}:"}; + my $name = $sname ? $sname : api::optionValue('template', $s->{size} == 0 ? 'code:class=handle' : 'code:class=struct', $obj, $def); + + return $name; +} + +sub findTemplate { + my $api = shift; + my $obj = shift; + my $s = shift; + my $sname = $s->{"$s->{type}:template"}; + my $def = $api->{index}->{"$s->{type}:"}; + my $name = $sname ? $sname : api::optionValue('template', $s->{size} == 0 ? 'code:class=handle' : 'code:class=struct', $obj, $def); + + print "template: $s->{name} -> $name\n" if ($api->{vars}->{verbose} > 1); + + return findTemplateName($api, $name); +} + # find value of first option of the given name sub optionValue { my $name = shift; @@ -399,30 +462,15 @@ sub addDependencies { } } -# use either {match} or {regex} to get all matches for a data type +# use either {match} or {regex} or {matcher} to get all matches for a data type sub findMatches { my $api = shift; my $inc = shift; my $ctx = shift; my $data = $api->{data}; - if ($inc->{match} eq 'func:') { - # or just last option? - my $code; - - if (defined($inc->{literal})) { - $code = 'sub { '.$inc->{literal}.' }'; - } else { - my @options = @{$inc->{options}}; - $code = 'sub { '.$options[$#options].'(@_) }'; - } - - my $match = eval $code; - - if (!defined($match)) { - die "unable to parse match function $inc->{match} $! $@"; - } - grep { $match->($_, $ctx) } grep { $_->{type} eq $inc->{type} } values %$data; + if ($inc->{matcher}) { + grep { $inc->{matcher}->($_, $ctx) } grep { $_->{type} eq $inc->{type} } values %$data; } else { my $s = $data->{$inc->{match}}; @@ -448,7 +496,7 @@ sub findDependencies { foreach my $inc (@{$obj->{items}}) { next if ($inc->{type} eq 'library'); - #print "? $inc->{regex}\n"; + print "? $inc->{match}\n"; foreach my $s (findMatches($api, $inc, $obj)) { my $n = "$s->{type}:$s->{name}"; @@ -461,9 +509,15 @@ sub findDependencies { } } elsif ($obj->{type} =~ m/^(struct|union|call|func|enum|define)$/) { #foreach my $n (grep { $_ =~ m/$obj->{regex}/ } keys %data) { + + print "? $obj->{name}\n"; + + foreach my $s (findMatches($api, $obj, $obj)) { my $n = "$s->{type}:$s->{name}"; + print "+ $n\n"; + $seen{$n}++; $s->{output} = 1; addDependencies($api, $obj, $s, $setdeps); @@ -528,6 +582,7 @@ sub loadAPIFile { } sub parseRename { + my $api = shift; my $how = shift; my $rename = $renameTable{'identity'}; @@ -539,6 +594,11 @@ sub parseRename { my $rx = qr/$1/; my $rp = $2; $rename = sub { my $s=shift; $s = $old->($s); $s =~ s/$rx/$rp/; return $s;}; + } elsif ($n =~ m@^func:(.*)$@) { + $rename = eval "sub { $1(\@_) }"; + if (!defined($rename)) { + die "Unable to parse rename function '$1'"; + } } elsif ($new) { $rename = sub { my $s=shift; $s = $old->($s); return $new->($s); }; } else { @@ -556,10 +616,6 @@ sub preprocess { # Find any anonymous types and add them in my %anonymous = (); foreach my $s (values %{$api->{data}}) { - # FIXME: fix the list names in export.cc and export-defines - $s->{items} = $s->{fields} if $s->{fields}; - $s->{items} = $s->{arguments} if $s->{arguments}; - $s->{items} = $s->{values} if $s->{values}; foreach my $m (grep { $_->{type} =~ m/struct:|union:/} @{$s->{items}}) { $anonymous{$m->{type}} = 1 if !defined($api->{data}->{$m->{type}}); @@ -605,18 +661,22 @@ sub analyseAPI { # Note that type:name regexes always start at the beginning foreach my $obj (@{$api->{api}}) { + $obj->{match} = "$obj->{type}:$obj->{name}"; + if ($obj->{name} =~ m@^/(.*)/$@) { $obj->{regex} = qr/^$obj->{type}:$1/; + } elsif ($obj->{name} =~ m@^$@) { + $obj->{matcher} = eval "sub { $1(\@_) }"; + die "unable to parse match function $obj->{name} $! $@" if !defined($obj->{matcher}); } else { $obj->{regex} = qr/^$obj->{type}:$obj->{name}$/; } - $obj->{match} = "$obj->{type}:$obj->{name}"; foreach my $opt (@{$obj->{options}}) { if ($opt =~ m/^(.+:rename)=(.*)$/) { - $obj->{$1} = parseRename($2); + $obj->{$1} = parseRename($api, $2); } elsif ($opt =~ m/^rename=(.*)$/) { - $obj->{"$obj->{type}:rename"} = parseRename($1); + $obj->{"$obj->{type}:rename"} = parseRename($api, $1); } } @@ -625,7 +685,7 @@ sub analyseAPI { my $match = $inc->{match}; my $mode = $defmode; - if ($inc->{match} =~ m/^(.*):(.*)$/) { + if ($inc->{match} =~ m/^(.*?):(.*)$/) { $match = $2; $mode = $1; } @@ -633,6 +693,12 @@ sub analyseAPI { $inc->{type} = $mode; if ($match =~ m@^/(.*)/$@) { $inc->{regex} = $mode ne 'field' ? qr/$mode:$1/ : qr/$1/; + } elsif ($match =~ m@^$@) { + $inc->{matcher} = eval "sub { $inc->{literal} }"; + die "unable to parse match function $inc->{literal} $! $@" if !defined($inc->{matcher}); + } elsif ($match =~ m@^$@) { + $inc->{matcher} = eval "sub { $1(\@_) }"; + die "unable to parse match function $inc->{match} $! $@" if !defined($inc->{matcher}); } else { $inc->{regex} = $mode ne 'field' ? qr/^$mode:$match$/ : qr/^$match$/; } @@ -640,7 +706,7 @@ sub analyseAPI { foreach my $opt (@{$inc->{options}}) { if ($opt =~ m/^rename=(.*)$/) { #print "option $opt ".Dumper($inc); - $inc->{"$mode:rename"} = parseRename($1); + $inc->{"$mode:rename"} = parseRename($api, $1); } } @@ -707,17 +773,19 @@ sub processField { my $def = $api->{index}->{"$s->{type}:"}; #print "process $s->{type}:$s->{name}.$m->{name}\n"; - #print " $m->{name}\n"; + print " $m->{name}\n" if ($api->{vars}->{verbose} > 1); foreach my $flag (@itemFlags) { my $value = optionFlag("$flag", $inc); $value = optionFlag("$flag:$m->{name}", $obj, $def) if !defined($value); $m->{$flag} = $value if (defined($value)); + $m->{select}->{$flag} = 1 if (defined($value)); } foreach my $option (@itemOptions) { my $value = optionValue("$option", undef, $inc); $value = optionValue("$option:$m->{name}", undef, $obj, $def) if !defined($value); $m->{$option} = $value if (defined($value)); + $m->{select}->{$option} = 1 if (defined($value)); } foreach my $option (@itemAnyOptions) { my $value = optionValue("$option", undef, $inc); @@ -727,7 +795,7 @@ sub processField { } $m->{output} = 1; - $m->{rename} = (first { defined $_ } $inc->{'field:rename'}, $obj->{'field:rename'}, $def->{'field:rename'}, $renameTable{identity})->($m->{name}); + $m->{rename} = (first { defined $_ } $inc->{'field:rename'}, $obj->{'field:rename'}, $def->{'field:rename'}, $renameTable{identity})->($m->{name}, $m, $s); } # process func or call main type @@ -750,7 +818,7 @@ sub processTypeFunc { processField($api, $obj, $inc, $s, $m); } - $s->{rename} = (first { defined $_ } $obj->{"$s->{type}:rename"}, $renameTable{identity})->($s->{name}); + $s->{rename} = (first { defined $_ } $obj->{"$s->{type}:rename"}, $renameTable{identity})->($s->{name}, $s); $s->{access} = optionValue('access', '', $obj, $def); $v = optionValue('onsuccess', undef, $obj, $def); $s->{onsuccess} = $v if defined $v; @@ -768,36 +836,44 @@ sub processFunc { my $obj = shift; my $inc = shift; my $s = shift; + my @params = (defined($s->{result}) ? $s->{result} : (), @{$s->{items}}); my $index = -1; + my $lindex = -2 - $#params; my $def = $api->{index}->{"$s->{type}:"}; my $v; print "process $s->{type}:$s->{name}\n" if ($api->{vars}->{verbose} > 1); foreach my $m (defined($s->{result}) ? $s->{result} : (), @{$s->{items}}) { - #print " $m->{name}\n"; + #print "[$index] [$lindex] $m->{name}\n"; foreach my $flag (@itemFlags) { my $value = optionFlag("$flag:$m->{name}", $inc, $obj, $def); $value = optionFlag("$flag:$index", $inc, $obj, $def) if !defined($value); + $value = optionFlag("$flag:$lindex", $inc, $obj, $def) if !defined($value); $m->{$flag} = $value if (defined($value)); + $m->{select}->{$flag} = 1 if (defined($value)); } foreach my $option (@itemOptions) { my $value = optionValue("$option:$m->{name}", undef, $inc, $obj, $def); $value = optionValue("$option:$index", undef, $inc, $obj, $def) if !defined($value); + $value = optionValue("$option:$lindex", undef, $inc, $obj, $def) if !defined($value); $m->{$option} = $value if (defined($value)); + $m->{select}->{$option} = 1 if (defined($value)); } foreach my $option (@itemAnyOptions) { my $value = optionValue("$option:$m->{name}", undef, $inc, $obj, $def); $value = optionValue("$option:$index", undef, $inc, $obj, $def) if !defined($value); + $value = optionValue("$option:$lindex", undef, $inc, $obj, $def) if !defined($value); $value = optionValue("$option", undef, $inc, $obj, $def) if !defined($value); $m->{$option} = $value if (defined($value)); } $m->{output} = 1; $index++; + $lindex++; } - $s->{rename} = (first { defined $_ } $inc->{"$s->{type}:rename"}, $obj->{"$s->{type}:rename"}, $renameTable{identity})->($s->{name}); + $s->{rename} = (first { defined $_ } $inc->{"$s->{type}:rename"}, $obj->{"$s->{type}:rename"}, $renameTable{identity})->($s->{name}, $s); $s->{access} = optionValue('access', '', $inc, $obj, $def); $v = optionValue('onsuccess', undef, $inc, $obj, $def); $s->{onsuccess} = $v if defined ($v); @@ -877,7 +953,7 @@ sub processType { next if ($seen->{"$s->{type}:$s->{name}"}++); - print "process type $s->{type}:$s->{name}\n" if ($api->{vars}->{verbose} > 1); + print "process type $obj->{match} $s->{type}:$s->{name}\n" if ($api->{vars}->{verbose} > 1); # process the struct/union fields first foreach my $inc (grep { $_->{type} eq 'field' } @{$obj->{items}}) { @@ -891,13 +967,13 @@ sub processType { processFields($api, $memberseen, $obj, undef, $s, @{$s->{items}}); } - $s->{rename} = (first { defined $_ } $obj->{"$s->{type}:rename"}, $def->{"$s->{type}:rename"}, $renameTable{identity})->($s->{name}); + $s->{rename} = (first { defined $_ } $obj->{"$s->{type}:rename"}, $def->{"$s->{type}:rename"}, $renameTable{identity})->($s->{name}, $s); # finish off postProcessType($s); # handle other types included/mark them no-output - $seen->{"$s->{type}:$s->{name}"} = 0; + #$seen->{"$s->{type}:$s->{name}"} = 0; processLibrary($api, $seen, $obj, $s); } @@ -910,10 +986,11 @@ sub processLibrary { my $ctx = shift; my $data = $api->{data}; - return if ($seen->{"$lib->{type}:$lib->{name}"}++); + #return if ($seen->{"$ctx->{type}:$ctx->{name}"}++); - print "process library $lib->{type}:$lib->{name}\n"; + print "process library $ctx->{type}:$ctx->{name}\n" if ($api->{vars}->{verbose} > 0); #print 'lib='.Dumper($lib); + #print Dumper($api->{api}); die; #print 'lib.options='.Dumper($lib->{options}); # TODO: embedded types @@ -937,6 +1014,7 @@ sub processLibrary { } } elsif ($inc->{type} eq 'library') { foreach my $l (grep { "$_->{type}:$_->{name}" =~ m/$inc->{regex}/ } @{$api->{api}}) { + print " -> $l->{name}\n"; # included libraries are never output directly $l->{output} = 0; processLibrary($api, $seen, $l, $ctx); diff --git a/src/notzed.nativez/lib/code.api b/src/notzed.nativez/lib/code.api index c5ff6e9..3c8a185 100644 --- a/src/notzed.nativez/lib/code.api +++ b/src/notzed.nativez/lib/code.api @@ -162,6 +162,7 @@ public class {rename} implements Pointer { private {rename}(MemorySegment segment) { this.segment = segment; +{init} } public static {rename} create(MemorySegment segment) { @@ -211,6 +212,7 @@ public class {rename} implements Pointer, Array<{rename}> { private {rename}(MemorySegment segment) { this.segment = segment; +{init} } public static {rename} create(MemorySegment segment) { @@ -233,6 +235,10 @@ public class {rename} implements Pointer, Array<{rename}> { return MemoryAddress.NULL != address ? create(MemorySegment.ofAddress(address, LAYOUT.byteSize() * length, scope)) : null; } + public static {rename} createArray(long length, SegmentAllocator alloc) { + return create(alloc.allocateArray(LAYOUT, length)); + } + @Override public final MemoryAddress address() { return segment.address(); @@ -284,6 +290,7 @@ public class {rename} implements Pointer { private {rename}(MemoryAddress address, ResourceScope scope) { this.address = address; this.scope = scope; +{init} } public static {rename} create(MemoryAddress address, ResourceScope scope) { @@ -337,6 +344,7 @@ code getset { } }} geti set=value={getnative} set=segment=segment {{ + # TODO: final static VarHandle {name}$VI = MemoryLayout.sequenceLayout(LAYOUT).varHandle(PathElement.sequenceElement(), PathElement.groupElement("{name}")); public {type} get{rename}AtIndex(long index) { // option a: resolve an offset segment and asme as above with set=segment=segment // option b: an indexed varhandle @@ -357,6 +365,34 @@ code getset { }} } +# set requires allocations, not sure if it should be Frame or Scope? +code getset-frame { + get set=value={getnative} {{ + public {type} get{rename}() { + return {tojava}; + } + }} + geti set=value={getnative} set=segment=segment {{ + public {type} get{rename}AtIndex(long index) { + // option a: resolve an offset segment and asme as above with set=segment=segment + // option b: an indexed varhandle + MemorySegment segment = segment().asSlice(index * LAYOUT.byteSize(), LAYOUT.byteSize()); + return {tojava}; + } + }} + set set=value=value {{ + public void set{rename}(Frame frame$, {type} value) { + {setnative}; + } + }} + seti set=value=value set=segment=segment {{ + public void set{rename}AtIndex(Frame frame$, long index, {type} value) { + MemorySegment segment = segment().asSlice(index * LAYOUT.byteSize(), LAYOUT.byteSize()); + {setnative}; + } + }} +} + code getsetelement { get set=index=i {{ public {typei} get{rename}Element(long i) { diff --git a/src/notzed.nativez/lib/code.pm b/src/notzed.nativez/lib/code.pm index bfd8d6d..9383602 100644 --- a/src/notzed.nativez/lib/code.pm +++ b/src/notzed.nativez/lib/code.pm @@ -200,6 +200,7 @@ sub formatDefine { sub formatEnum { my $api = shift; my $s = shift; + my $seen = shift; my $type = $defineType{$s->{value_type}}; my $d = join "\n\t", "// enum $s->{name}", map { @@ -207,30 +208,52 @@ sub formatEnum { my $value = $_->{value}; "public static final $type $name = $typePrefix{$type}$value$typeSuffix{$type};"; + } grep { + $seen->{"value:$_->{name}"}++ == 0 } @{$s->{items}}; return "\t$d\n"; } +# somewhat faster +# dunno how to ignore comments tho sub formatTemplate { my $template = shift; my $vars = shift; my $prefix = shift; my $result; - #print "apply-template vars=".Dumper($vars); - - while ($template =~ m/^(.*?)\{([\w-]+)\}(.*)$/s) { + while ($template =~ m/^(.*?)\{([\w-]+)\}/spo) { $result .= $1; - #print " $2 -> $vars->{$2}\n"; + if (defined $vars->{$2}) { + $template = $vars->{$2}.${^POSTMATCH}; + } else { + $result .= "{$2}"; + $template = ${^POSTMATCH}; + } + } + $result .= $template; + $result =~ s/^/$prefix/gm; + $result; +} + +# Format a template without recursive application +sub formatTemplateStream { + my $template = shift; + my $vars = shift; + my $prefix = shift; + my $result; + + while ($template =~ m/^(.*?)\{([\w-]+)\}/spo) { + $result .= $1; if (defined $vars->{$2}) { - $template = $vars->{$2}.$3; + $result .= $vars->{$2}; } else { $result .= "{$2}"; - $template = $3; } + $template = ${^POSTMATCH}; } $result .= $template; $result =~ s/^/$prefix/gm; @@ -242,13 +265,16 @@ sub formatTemplate { sub applyTemplate { my $code = shift; my $match = shift; + my $stream = shift; my $vars = \%{$match}; + #print "template: $code->{match}\n"; + foreach my $set (api::optionValuesAll('set', $code)) { $vars->{$1} = $2 if ($set =~ m/^(\w+)=(.*)/); } - formatTemplate($code->{literal}, $vars); + $stream ? formatTemplateStream($code->{literal}, $vars) : formatTemplate($code->{literal}, $vars); } sub bitfieldSize { diff --git a/src/notzed.nativez/lib/config.pm b/src/notzed.nativez/lib/config.pm index 3b4cdff..6fb67a3 100644 --- a/src/notzed.nativez/lib/config.pm +++ b/src/notzed.nativez/lib/config.pm @@ -86,13 +86,14 @@ sub loadControlFile { push @{$self->{pragmas}}, \@pragma; } } elsif ($tokeniser->{type} eq 'token') { - $target = { type => $t, options => [], items => [] }; + $target = { type => $t, options => [], items => [], file => "$tokeniser->{file}->{path}:$tokeniser->{file}->{lineno}" }; push @$list, $target; $state = 1; } else { die("$tokeniser->{file}->{path}:$tokeniser->{file}->{lineno}:$tokeniser->{colno}: expected type token"); } } elsif ($state == 1) { + # token [token] if ($tokeniser->{type} eq 'token') { $target->{name} = $t; $state = 2; @@ -100,6 +101,7 @@ sub loadControlFile { die("$tokeniser->{file}->{path}:$tokeniser->{file}->{lineno}:$tokeniser->{colno}: expected match token"); } } elsif ($state == 2) { + # token token token* [ '{' | literal ] if ($t eq '{') { $state = 3; } elsif ($tokeniser->{type} eq 'literal') { diff --git a/src/notzed.nativez/lib/method.pm b/src/notzed.nativez/lib/method.pm index 2476803..c835f98 100644 --- a/src/notzed.nativez/lib/method.pm +++ b/src/notzed.nativez/lib/method.pm @@ -19,6 +19,9 @@ sub fieldScope { sub fieldScopeAction { my $m = shift; + # TBH this just isn't going to work, scopes are so limited in use + return (); + if ($m->{field}->{scope} =~ m/^explicit,(.*)$/) { return code::formatTemplate("scope\$.addCloseAction(() -> $1;", { %{$m->{match}}, value => 'res$' }); } elsif ($m->{field}->{scope} =~ m/^explicit$/) { @@ -146,7 +149,7 @@ sub new { $info->{'java-result-return'} = 'return res$;'; $info->{'trampoline-result-define'} = apply('{type} res$ = ', $result); - $info->{'trampoline-result-return'} = code::formatTemplate('return {tonative};', { %{$result->{match}}, value => 'res$' }); + $info->{'trampoline-result-return'} = code::formatTemplate('return ({type}){tonative};', { %{$result->{match}}, value => 'res$' }); } else { $info->{'java-result'} = 'void'; $info->{'java-result-assign'} = ''; diff --git a/src/notzed.nativez/lib/types.api b/src/notzed.nativez/lib/types.api index 671a7bd..0f219b5 100644 --- a/src/notzed.nativez/lib/types.api +++ b/src/notzed.nativez/lib/types.api @@ -1,8 +1,7 @@ # -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*- # -# TODO: add a 'mode' to type matching -# this woudld allow per-field overrides, e.g. char * field use String or MemorySegment or ByteArray depending on option +# TODO: use select=array rather than inline logic to select alternatives? maybe? # special case for bitfields # TODO: well this is bit of a mess, maybe should use like code:method? @@ -74,29 +73,38 @@ type /^\[(?\d+)u64:\$\{(\w+)\}\]$/ { } # 1d array of (struct or union or function?) -type /^\[(?\d+)\$\{(\w+)\}\]$/ { +type /^\[(?\d+)\$\{(\w+)\}\]$/ template=code:getbyvalue { type {{ "$data->{$m->{type}}->{rename}" }} layout {{ "MemoryLayout.sequenceLayout({length}, {type}.LAYOUT)" }} + + getsegment {{ '(MemorySegment){name}$SH.invokeExact({segment})' }} + getnative {{ '{type}.create({getsegment})' }} + + varhandle {{ + 'final static MethodHandle {name}$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement("{name}"));'."\n". + '//final static VarHandle {name}$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement("{name}"), MemoryLayout.PathElement.sequenceElement());'."\n" + }} + } # 1d array of primitive type /^\[(?\d+)(?[uif]\d+)\]$/ copy= template=code:getbyvalue,code:getsetelement { - layout {{ "MemoryLayout.sequenceLayout({length}, Memory.".uc($typeSizes{$match->{ctype}}).")" }} - type {{ ucfirst($typeSizes{$match->{ctype}})."Array" }} - tojava {{ "{type}.createArray({value}, $match->{length}, {scope})" }} - tonative {{ "(Addressable)Memory.address({value}" }} - - getnative {{ "{type}.create((MemorySegment)$m->{name}\$SH.invokeExact({segment}))" }} + layout {{ "MemoryLayout.sequenceLayout({length}, Memory.".uc($typeSizes{$match->{ctype}}).")" }} + type {{ ucfirst($typeSizes{$match->{ctype}})."Array" }} + tojava {{ "{type}.createArray({value}, $match->{length}, {scope})" }} + tonative {{ "(Addressable)Memory.address({value})" }} + lea {{ "(MemorySegment)$m->{name}\$SH.invokeExact({segment})" }} + getnative {{ "{type}.create({lea})" }} setnative; - varhandle {{ + varhandle {{ "final static MethodHandle $m->{name}\$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"));\n". "final static VarHandle $m->{name}\$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"), MemoryLayout.PathElement.sequenceElement());\n" }} - typei {{ "$typeSizes{$match->{ctype}}" }} - getnativei {{ "({typei})$m->{name}\$EH.get({segment}, {index})" }} - setnativei {{ "$m->{name}\$EH.set({segment}, {index}, ({typei}){value})" }} + typei {{ "$typeSizes{$match->{ctype}}" }} + getnativei {{ "({typei})$m->{name}\$EH.get({segment}, {index})" }} + setnativei {{ "$m->{name}\$EH.set({segment}, {index}, ({typei}){value})" }} } # 2d array of primitive @@ -111,12 +119,12 @@ type /^\[(?\d+)\[(?\d+)(?[uif]\d+)\]\]$/ setnative; typei {{ "$typeSizes{$match->{ctype}}" }} - getnativei {{ "({typei})$m->{name}\$EH.get({segment}, {index0}, {index1}))" }} - setnativei {{ "$m->{name}\$EH.set({segment}, {index0}, {index1}, ({typei}){value}))" }} + getnativei {{ "({typei})$m->{name}\$EH.get({segment}, {index0}, {index1})" }} + setnativei {{ "$m->{name}\$EH.set({segment}, {index0}, {index1}, ({typei}){value})" }} varhandle {{ "final static MethodHandle $m->{name}\$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"));\n". - "final static VarHandle $m->{name}\$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"), MemoryLayout.PathElement.sequenceElement(), MemotyLayout.PathElement.sequenceElement());\n" + "final static VarHandle $m->{name}\$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"), MemoryLayout.PathElement.sequenceElement(), MemoryLayout.PathElement.sequenceElement());\n" }} } @@ -230,7 +238,8 @@ type /^u64:\$\{(\w+)\}$/ copy= { # *void with size type /^u64:v$/ select=array-size copy= { type {{ 'MemorySegment' }} - tojava {{ "MemorySegment.ofAddress({value}, $m->{'array-size'}->{name} * 8, {scope})" }} + length {{ 'get'.($m->{'array-size'}->{rename}).'()' }} + tojava {{ "MemorySegment.ofAddress({value}, {length}, {scope})" }} } # *void diff --git a/src/notzed.vkheader.test/classes/module-info.java b/src/notzed.vkheader.test/classes/module-info.java new file mode 100644 index 0000000..489e41b --- /dev/null +++ b/src/notzed.vkheader.test/classes/module-info.java @@ -0,0 +1,6 @@ + +module notzed.vkheader.test { + requires notzed.vkheader; + + requires java.desktop; +} diff --git a/src/notzed.vkheader/classes/vulkan/test/TestVulkan.java b/src/notzed.vkheader.test/classes/vulkan/test/TestMandelbrot.java similarity index 87% rename from src/notzed.vkheader/classes/vulkan/test/TestVulkan.java rename to src/notzed.vkheader.test/classes/vulkan/test/TestMandelbrot.java index be3f524..b7305fb 100755 --- a/src/notzed.vkheader/classes/vulkan/test/TestVulkan.java +++ b/src/notzed.vkheader.test/classes/vulkan/test/TestMandelbrot.java @@ -61,19 +61,9 @@ import au.notzed.nativez.*; import vulkan.*; -import static vulkan.VkBufferUsageFlagBits.*; -import static vulkan.VkMemoryPropertyFlagBits.*; -import static vulkan.VkSharingMode.*; -import static vulkan.VkDescriptorType.*; -import static vulkan.VkShaderStageFlagBits.*; -import static vulkan.VkCommandBufferLevel.*; -import static vulkan.VkCommandBufferUsageFlagBits.*; -import static vulkan.VkPipelineBindPoint.*; - -import static vulkan.VkDebugUtilsMessageSeverityFlagBitsEXT.*; -import static vulkan.VkDebugUtilsMessageTypeFlagBitsEXT.*; - -public class TestVulkan { +import static vulkan.VkConstants.*; + +public class TestMandelbrot { static final boolean debug = true; ResourceScope scope = ResourceScope.newSharedScope(); @@ -116,7 +106,7 @@ public class TestVulkan { if (!debug) return; try (Frame frame = Frame.frame()) { - NativeSymbol cb = PFN_vkDebugUtilsMessengerCallbackEXT.of((severity, flags, data) -> { + var cb = PFN_vkDebugUtilsMessengerCallbackEXT.upcall((severity, flags, data, dummy) -> { System.out.printf("Debug: %d: %s\n", severity, data.getMessage()); return 0; }, scope); @@ -126,16 +116,26 @@ public class TestVulkan { | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT, - cb.address(), + cb, null); - logger = instance.vkCreateDebugUtilsMessengerEXT(info, null); + logger = instance.vkCreateDebugUtilsMessengerEXT(info, null, scope); } //typedef VkBool32 (*PFN_vkDebugUtilsMessengerCallbackEXT)(VkDebugUtilsMessageSeverityFlagBitsEXT, VkDebugUtilsMessageTypeFlagsEXT, const VkDebugUtilsMessengerCallbackDataEXT *, void *); } + void dumpStructure(GroupLayout layout, MemorySegment s) { + System.out.println(s.address()); + for (MemoryLayout m: layout.memberLayouts()) { + if (m instanceof jdk.incubator.foreign.ValueLayout) { + var vh = layout.varHandle(jdk.incubator.foreign.MemoryLayout.PathElement.groupElement(m.name().get())); + System.out.printf(" %s: %s\n", m.name().get(), vh.get(s)); + } + } + } + void init_instance() throws Exception { try (Frame frame = Frame.frame()) { VkInstanceCreateInfo info = VkInstanceCreateInfo.create(frame, @@ -145,18 +145,17 @@ public class TestVulkan { debug ? new String[] { "VK_EXT_debug_utils" } : null ); - instance = VkInstance.vkCreateInstance(info, null); + instance = VkInstance.vkCreateInstance(info, null, scope); } } void init_device() throws Exception { try (Frame frame = Frame.frame()) { - IntArray count$h = IntArray.create(frame, 1); HandleArray devs; int count; int res; - devs = instance.vkEnumeratePhysicalDevices(); + devs = instance.vkEnumeratePhysicalDevices(frame, scope); int best = 0; int devid = -1; @@ -164,21 +163,15 @@ public class TestVulkan { for (int i=0;i best) { @@ -200,26 +193,24 @@ public class TestVulkan { frame, 0, queueid, - 1, qpri); VkDeviceCreateInfo devinfo = VkDeviceCreateInfo.create( frame, 0, - 1, qinfo, null, null, null); - device = physicalDevice.vkCreateDevice(devinfo, null); + device = physicalDevice.vkCreateDevice(devinfo, null, scope); System.out.printf("device = %s\n", device.address()); // NOTE: app scope - deviceMemoryProperties = VkPhysicalDeviceMemoryProperties.create(scope); + deviceMemoryProperties = VkPhysicalDeviceMemoryProperties.create((SegmentAllocator)scope); physicalDevice.vkGetPhysicalDeviceMemoryProperties(deviceMemoryProperties); - computeQueue = device.vkGetDeviceQueue(queueid, 0); + computeQueue = device.vkGetDeviceQueue(queueid, 0, scope); } } @@ -238,10 +229,9 @@ public class TestVulkan { dataSize, usage, VK_SHARING_MODE_EXCLUSIVE, - 0, null); - VkBuffer buffer = device.vkCreateBuffer(buf_info, null); + VkBuffer buffer = device.vkCreateBuffer(buf_info, null, scope); device.vkGetBufferMemoryRequirements(buffer, req); @@ -249,7 +239,7 @@ public class TestVulkan { req.getSize(), find_memory_type(deviceMemoryProperties, req.getMemoryTypeBits(), properties)); - VkDeviceMemory memory = device.vkAllocateMemory(alloc, null); + VkDeviceMemory memory = device.vkAllocateMemory(alloc, null, scope); device.vkBindBufferMemory(buffer, memory, 0); @@ -277,10 +267,9 @@ public class TestVulkan { VkDescriptorSetLayoutCreateInfo descriptor_layout = VkDescriptorSetLayoutCreateInfo.create(frame, 0, - 1, layout_binding); - descriptorSetLayout = device.vkCreateDescriptorSetLayout(descriptor_layout, null); + descriptorSetLayout = device.vkCreateDescriptorSetLayout(descriptor_layout, null, scope); /* Create descriptor pool */ VkDescriptorPoolSize type_count = VkDescriptorPoolSize.create(frame, @@ -290,10 +279,9 @@ public class TestVulkan { VkDescriptorPoolCreateInfo descriptor_pool = VkDescriptorPoolCreateInfo.create(frame, 0, 1, - 1, type_count); - descriptorPool = device.vkCreateDescriptorPool(descriptor_pool, null); + descriptorPool = device.vkCreateDescriptorPool(descriptor_pool, null, scope); /* Allocate from pool */ HandleArray layout_table = VkDescriptorSetLayout.createArray(1, frame); @@ -317,7 +305,6 @@ public class TestVulkan { descriptorSets.getAtIndex(0), 0, 0, - 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, null, bufferInfo, @@ -338,7 +325,7 @@ public class TestVulkan { mandelbrot_cs.length() * 4, mandelbrot_cs); - mandelbrotShader = device.vkCreateShaderModule(vsInfo, null); + mandelbrotShader = device.vkCreateShaderModule(vsInfo, null, scope); /* Link shader to layout */ HandleArray layout_table = VkDescriptorSetLayout.createArray(1, frame); @@ -349,10 +336,9 @@ public class TestVulkan { 0, 1, layout_table, - 0, null); - pipelineLayout = device.vkCreatePipelineLayout(pipelineinfo, null); + pipelineLayout = device.vkCreatePipelineLayout(pipelineinfo, null, scope); /* Create pipeline */ VkComputePipelineCreateInfo pipeline = VkComputePipelineCreateInfo.create(frame, @@ -365,7 +351,7 @@ public class TestVulkan { stage.setStage(VK_SHADER_STAGE_COMPUTE_BIT); stage.setModule(mandelbrotShader); - stage.setName(frame, mandelbrot_entry); + stage.setName(mandelbrot_entry); device.vkCreateComputePipelines(null, 1, pipeline, null, computePipeline); } @@ -380,7 +366,7 @@ public class TestVulkan { 0, computeQueueIndex); - commandPool = device.vkCreateCommandPool(poolinfo, null); + commandPool = device.vkCreateCommandPool(poolinfo, null, scope); VkCommandBufferAllocateInfo cmdinfo = VkCommandBufferAllocateInfo.create(frame, commandPool, @@ -388,7 +374,8 @@ public class TestVulkan { 1); // should it take a scope? - commandBuffers = device.vkAllocateCommandBuffers(cmdinfo); + commandBuffers = VkCommandBuffer.createArray(instance, 1, (SegmentAllocator)scope, scope); + device.vkAllocateCommandBuffers(cmdinfo, commandBuffers); /* Fill command buffer with commands for later operation */ VkCommandBufferBeginInfo beginInfo = VkCommandBufferBeginInfo.create(frame, @@ -417,8 +404,8 @@ public class TestVulkan { try (Frame frame = Frame.frame()) { VkSubmitInfo submitInfo = VkSubmitInfo.create(frame); - submitInfo.setCommandBufferCount(0, 1); - submitInfo.setCommandBuffers(0, commandBuffers); + submitInfo.setCommandBufferCount(1); + submitInfo.setCommandBuffers(commandBuffers); /* Create fence to mark the task completion */ VkFence fence; @@ -426,7 +413,8 @@ public class TestVulkan { VkFenceCreateInfo fenceInfo = VkFenceCreateInfo.create(frame); // maybe this should take a HandleArray rather than being a constructor - fence = device.vkCreateFence(fenceInfo, null); + // FIXME: some local scope + fence = device.vkCreateFence(fenceInfo, null, scope); fences.set(0, fence); /* Await completion */ @@ -436,7 +424,7 @@ public class TestVulkan { int res; do { res = device.vkWaitForFences(1, fences, VK_TRUE, 1000000); - } while (res == VkResult.VK_TIMEOUT); + } while (res == VK_TIMEOUT); device.vkDestroyFence(fence, null); } @@ -546,7 +534,7 @@ public class TestVulkan { IntArray loadSPIRV0(String name) throws IOException { // hmm any way to just load this directly? - try (InputStream is = TestVulkan.class.getResourceAsStream(name)) { + try (InputStream is = TestMandelbrot.class.getResourceAsStream(name)) { ByteBuffer bb = ByteBuffer.allocateDirect(8192).order(ByteOrder.nativeOrder()); int length = Channels.newChannel(is).read(bb); @@ -558,7 +546,7 @@ public class TestVulkan { } IntArray loadSPIRV(String name) throws IOException { - try (InputStream is = TestVulkan.class.getResourceAsStream(name)) { + try (InputStream is = TestMandelbrot.class.getResourceAsStream(name)) { MemorySegment seg = ((SegmentAllocator)scope).allocateArray(Memory.INT, 2048); int length = Channels.newChannel(is).read(seg.asByteBuffer()); @@ -573,7 +561,7 @@ public class TestVulkan { VkMemoryType mtypes = memory.getMemoryTypes(); for (int i = 0; i < memory.getMemoryTypeCount(); i++) { - if (((1 << i) & typeMask) != 0 && ((mtypes.getPropertyFlags(i) & query) == query)) + if (((1 << i) & typeMask) != 0 && ((mtypes.getPropertyFlagsAtIndex(i) & query) == query)) return i; } return -1; @@ -588,6 +576,7 @@ public class TestVulkan { init_instance(); init_debug(); + init_device(); dst = init_buffer(dstBufferSize, @@ -614,6 +603,6 @@ public class TestVulkan { public static void main(String[] args) throws Throwable { System.loadLibrary("vulkan"); - new TestVulkan().demo(); + new TestMandelbrot().demo(); } } diff --git a/src/notzed.vkregistry/gen/cube.frag b/src/notzed.vkheader.test/gen/cube.frag similarity index 100% rename from src/notzed.vkregistry/gen/cube.frag rename to src/notzed.vkheader.test/gen/cube.frag diff --git a/src/notzed.vkregistry/gen/cube.vert b/src/notzed.vkheader.test/gen/cube.vert similarity index 100% rename from src/notzed.vkregistry/gen/cube.vert rename to src/notzed.vkheader.test/gen/cube.vert diff --git a/src/notzed.vkheader.test/gen/gen.make b/src/notzed.vkheader.test/gen/gen.make new file mode 100644 index 0000000..9099112 --- /dev/null +++ b/src/notzed.vkheader.test/gen/gen.make @@ -0,0 +1,18 @@ + + +bin/status/notzed.vkheader.test.classes: \ + bin/modules/notzed.vkheader.test/vulkan/test/mandelbrot.bin \ + bin/modules/notzed.vkheader.test/vulkan/test/cube_vs.bin \ + bin/modules/notzed.vkheader.test/vulkan/test/cube_fs.bin + +bin/modules/notzed.vkheader.test/vulkan/test/mandelbrot.bin: src/notzed.vkheader.test/gen/mandelbrot.comp + mkdir -p $(@D) + glslangValidator --target-env vulkan1.0 -V -o $@ $< + +bin/modules/notzed.vkheader.test/vulkan/test/cube_vs.bin: src/notzed.vkheader.test/gen/cube.vert + mkdir -p $(@D) + glslangValidator --target-env vulkan1.0 -V -o $@ $< + +bin/modules/notzed.vkheader.test/vulkan/test/cube_fs.bin: src/notzed.vkheader.test/gen/cube.frag + mkdir -p $(@D) + glslangValidator --target-env vulkan1.0 -V -o $@ $< diff --git a/src/notzed.vkregistry/gen/mandelbrot.comp b/src/notzed.vkheader.test/gen/mandelbrot.comp similarity index 100% rename from src/notzed.vkregistry/gen/mandelbrot.comp rename to src/notzed.vkheader.test/gen/mandelbrot.comp diff --git a/src/notzed.vkheader/classes/module-info.java b/src/notzed.vkheader/classes/module-info.java index 012bfac..ed4a816 100644 --- a/src/notzed.vkheader/classes/module-info.java +++ b/src/notzed.vkheader/classes/module-info.java @@ -2,5 +2,5 @@ module notzed.vkheader { requires transitive notzed.nativez; - requires java.desktop; + exports vulkan; } diff --git a/src/notzed.vkheader/gen/gen.make b/src/notzed.vkheader/gen/gen.make index cf30e7d..54da390 100644 --- a/src/notzed.vkheader/gen/gen.make +++ b/src/notzed.vkheader/gen/gen.make @@ -1,4 +1,3 @@ - notzed.vkheader_API = vkheader notzed.vkheader_APIFLAGS = -t vulkan -Isrc/notzed.vkheader/gen diff --git a/src/notzed.vkheader/gen/vkheader.api b/src/notzed.vkheader/gen/vkheader.api index 4c93262..27b5f11 100644 --- a/src/notzed.vkheader/gen/vkheader.api +++ b/src/notzed.vkheader/gen/vkheader.api @@ -1,49 +1,247 @@ # -*- Mode:text; tab-width:4; electric-indent-mode: nil; indent-line-function:insert-tab; -*- +# FIXME: struct init for an array of struct needs to set the sType fields if appropriate +# FIXME: extension function pointers resolved and stored per-object, should just be stored on the vkinstance or something + +# these are for the set-all constructors + +# TODO: the set String and String[] things should be the same, +# either use the segment scope to allocate or take the segment as an argument +# probably the former + +# **char, i.e. char *array[] +type /^u64:u64:i8$/ copy= +# select=vkstring template=code:getset-frame +{ + length {{ 'get'.($m->{'array-size'}->{rename}).'()' }} + type {{ 'String[]' }} + tojava {{ 'Memory.toStringArray({value}, {length})' }} +# tonative {{ 'frame$.copy({value})' }} + tonative {{ 'Frame.copy({segment}, {value}).address()' }} + typei {{ 'String' }} +} + +# *char, i.e. string +type /^u64:(?i8)$/ copy= { + type {{ 'String' }} + tonative {{ '(Addressable)({value} != null ? frame$.copy({value}) : MemoryAddress.NULL)' }} + + setnative {{ '{name}$VH.set({segment}, Frame.copy({segment}, {value}).address())' }} +# copynative {{ 'SegmentAllocator.nativeAllocator({segment}.scope()).allocateUtf8String({value})' }} +# copynative {{ '((SegmentAllocator){segment}).allocateUtf8String({value})' }} +# copynative {{ 'frame$.copy({value})' }} + + tojava {{ '({value}).getUtf8String(0L)' }} +} + +# 1d array of primitive +type /^\[(?\d+)(?[uif]\d+)\]$/ select=vkarray + copy= template=code:getbyvalue,code:getsetelement { + layout {{ "MemoryLayout.sequenceLayout({length}, {layouti})" }} +# type {{ ucfirst($typeSizes{$match->{ctype}})."Array" }} + type {{ $typeSizes{$match->{ctype}}."[]" }} + tojava {{ "{type}.createArray({value}, $match->{length}, {scope})" }} + tonative {{ "(Addressable)Memory.address({value})" }} + lea {{ "(MemorySegment)$m->{name}\$SH.invokeExact({segment})" }} + getnative {{ "({lea}).toArray({layouti})" }} + setnative {{ '({lea}).copyFrom(MemorySegment.ofArray({value}))' }} + varhandle {{ + "final static MethodHandle $m->{name}\$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"));\n". + "final static VarHandle $m->{name}\$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"), MemoryLayout.PathElement.sequenceElement());\n" + }} + + typei {{ "$typeSizes{$match->{ctype}}" }} + layouti {{ 'Memory.'.uc($typeSizes{$match->{ctype}}) }} + getnativei {{ "({typei})$m->{name}\$EH.get({segment}, {index})" }} + setnativei {{ "$m->{name}\$EH.set({segment}, {index}, ({typei}){value})" }} +} + +# 2d array of primitive +type /^\[(?\d+)\[(?\d+)(?[uif]\d+)\]\]$/ select=vkarray + copy= template=code:getbyvalue,code:getsetelement2d { + layout {{ "MemoryLayout.sequenceLayout({length0}, MemoryLayout.sequenceLayout({length1}, Memory.".uc($typeSizes{$match->{ctype}})."))" }} +# type {{ ucfirst($typeSizes{$match->{ctype}})."Array" }} + type {{ $typeSizes{$match->{ctype}}."[]" }} + tojava {{ "{type}.create({value})" }} + tonative {{ "(Addressable)Memory.address({value})" }} + + lea {{ "(MemorySegment)$m->{name}\$SH.invokeExact({segment})" }} + getnative {{ "({lea}).toArray({layouti})" }} + setnative {{ '({lea}).copyFrom(MemorySegment.ofArray({value}))' }} + + typei {{ "$typeSizes{$match->{ctype}}" }} + layouti {{ 'Memory.'.uc($typeSizes{$match->{ctype}}) }} + getnativei {{ "({typei})$m->{name}\$EH.get({segment}, {index0}, {index1})" }} + setnativei {{ "$m->{name}\$EH.set({segment}, {index0}, {index1}, ({typei}){value})" }} + + varhandle {{ + "final static MethodHandle $m->{name}\$SH = LAYOUT.sliceHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"));\n". + "final static VarHandle $m->{name}\$EH = LAYOUT.varHandle(MemoryLayout.PathElement.groupElement(\"$m->{name}\"), MemoryLayout.PathElement.sequenceElement(), MemoryLayout.PathElement.sequenceElement());\n" + }} +} + +# this passes instance to the create functions +type /^u64:u64:\$\{(\w+)\}$/ select=vkinstance copy= { + length {{ $m->{'array-size'} ? 'get'.($m->{'array-size'}->{rename}).'()' : 'Long.MAX_VALUE' }} + type {{ !$m->{array} ? 'PointerArray' : 'HandleArray<{typei}>' }} + tojava {{ + !$m->{array} + ? 'PointerArray.createArray({value}, {length}, {scope})' + : 'HandleArray.createArray({value}, {length}, (a, s) -> {typei}.create(instance, a, s), {scope})' + }} + # tojavai ... ? + carrieri {{ "MemoryAddress" }} + typei {{ "$data->{$m->{type}}->{rename}" }} + tojavai {{ "{typei}.create(instance, {value}, {scope})" }} +} + %include types.api; %include code.api; %require vkheader.pm; -struct rename=s/_T$// { +%stage.postprocess vkheader::postprocess; + +# field rename function +# field:remname=func:[function] +# [function] is a defined function that will take: +# function(name, member, struct) + +# TODO: make rename func and syntax the same +# perhaps field:rename= +# func: + +struct rename=s/_T$// field:rename=func:vkheader::renameField access=rw default=all { } struct VkInstance_T { - func:vkGetInstanceProcAddr raw:result$ instance:0; + # override this otherwise it wants to return PFN_vkVoidFunction + func:vkGetInstanceProcAddr {{ + static final MethodHandle vkGetInstanceProcAddr$FH = Memory.downcall("vkGetInstanceProcAddr", FunctionDescriptor.of(Memory.POINTER, Memory.POINTER, Memory.POINTER)); + public NativeSymbol vkGetInstanceProcAddr(String pName) { + MemoryAddress result$; + try (Frame frame$ = Frame.frame()) { + result$ = (MemoryAddress)vkGetInstanceProcAddr$FH.invokeExact((jdk.incubator.foreign.Addressable)address(), (Addressable)(pName != null ? frame$.copy(pName) : MemoryAddress.NULL)); + return NativeSymbol.ofAddress(pName, result$, scope()); + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + }} + func:; + func:; - func: instance:0 {{ - my $s = shift; - return defined($s->{items}->[0]) && $s->{items}->[0]->{deref} eq 'u64:${VkInstance_T}'; - }} + # a few helpers + code: {{ + public HandleArray vkEnumeratePhysicalDevices(SegmentAllocator alloc, ResourceScope scope) { + try (Frame frame = Frame.frame()) { + IntArray count = IntArray.createArray(1, frame); + vkEnumeratePhysicalDevices(count, null); + var list = VkPhysicalDevice.createArray(this, count.get(0), alloc, scope); + vkEnumeratePhysicalDevices(count, list); + return list; + } + } + }} +} + +struct VkDevice_T { + func:; + func:; + + code: {{ + public MemorySegment vkMapMemory(VkDeviceMemory memory, long offset, long size, int flags, ResourceScope scope$) { + try (Frame frame = Frame.frame()) { + PointerArray result = PointerArray.createArray(1, frame); + + vkMapMemory(memory, offset, size, flags, result); + + // possibly can make this readonly etc based on flags + return MemorySegment.ofAddress(result.getAtIndex(0), size, scope$); + } + } + }} - # another version for extension functions - uses a dynamic func resolution - #func: instance:0 func:template=code:vulkan-method=invoke-dynamic {{ - #}} +} + +struct VkPhysicalDevice_T { + func:; + func:; + + code: {{ + public VkQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties(SegmentAllocator alloc) { + try (Frame frame = Frame.frame()) { + IntArray count = IntArray.createArray(1, frame); + vkGetPhysicalDeviceQueueFamilyProperties(count, null); + VkQueueFamilyProperties props = VkQueueFamilyProperties.createArray(count.getAtIndex(0), alloc); + vkGetPhysicalDeviceQueueFamilyProperties(count, props); + return props; + } + } + }} } struct /Vk/ { - func: instance:0 vkheader::matchObjectFunction; + func:; + func:; +} + +call access=rw { +} + +call PFN_vkDebugUtilsMessengerCallbackEXT access=w { +} + +call PFN_vkGetDeviceProcAddr access=r { } -call /PFN_vk/ access=rw { +#call /PFN_vk/ access=rw { +#} + +func vkCreateInstance { + result$ success=VK_SUCCESS,1; + pInstance return; +} + +library VkConstants { + enum://; + define:extensions; } enum // { } -code vulkan-method { +define extensions vkheader.h { + /^VK_.*_(SPEC_VERSION|EXTENSION_NAME)$/ include; +} + +code vulkan { - invoke-dynamic {{ - MethodHandle {name}$FH; + create-all {{ + public static {java-result} create{rename}(Frame frame$, {java-arguments}) { + try { + {java-result} self$ = {java-result}.create(frame$); + {set-init} + {set-all} + return self$; + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + }} + + invoke-dynamic-init-instance {{ {name}$NS = vkGetInstanceProcAddr("{name}"); }} + invoke-dynamic-init {{ {name}$NS = instance.vkGetInstanceProcAddr("{name}"); }} + + invoke-dynamic {{ + final NativeSymbol {name}$NS; + final static MethodHandle {name}$DH = Memory.downcall({function-descriptor}); public {java-result} {rename}({java-arguments}) { {native-output-define} {native-result-define} try {create-frame}{ - if ({name}$FH == null) - {name}$FH = Memory.downcall("{name}", vkGetInstanceProcAddr("{name}"), {function-descriptor}, scope()); - {native-output-init} - {native-result-assign}{name}$FH.invokeExact({native-call}); + {native-result-assign}{name}$DH.invokeExact({name}$NS, {native-call}); {native-output-copy} {result-test}{ {java-result-assign} @@ -55,5 +253,48 @@ code vulkan-method { } {result-throw} } - }} + }} + + handle-dynamic {{ +package {package}; +import jdk.incubator.foreign.*; +import java.lang.invoke.*; +import au.notzed.nativez.*; + +public class {rename} implements Pointer { + + MemoryAddress address; + ResourceScope scope; + VkInstance instance; + + private {rename}(VkInstance instance, MemoryAddress address, ResourceScope scope) { + this.address = address; + this.scope = scope; + this.instance = instance; + {init} + } + + public static {rename} create(VkInstance instance, MemoryAddress address, ResourceScope scope) { + return MemoryAddress.NULL != address ? new {rename}(instance, address, scope) : null; + } + + public static HandleArray<{rename}> createArray(VkInstance instance, long count, SegmentAllocator alloc, ResourceScope scope) { + return HandleArray.createArray(count, alloc, (a, s)-> create(instance, a, s), scope); + } + + @Override + public MemoryAddress address() { + return address; + } + + @Override + public ResourceScope scope() { + return scope; + } + + {defines} + {enums} + {methods} +} +}} } diff --git a/src/notzed.vkheader/gen/vkheader.pm b/src/notzed.vkheader/gen/vkheader.pm index 4411aa3..bee1f34 100644 --- a/src/notzed.vkheader/gen/vkheader.pm +++ b/src/notzed.vkheader/gen/vkheader.pm @@ -1,39 +1,665 @@ +# +# TODO: constructors initialise sType +# package vkheader; +use strict; + +use Time::HiRes qw(clock_gettime CLOCK_REALTIME); + use Data::Dumper; +use XML::Parser; -# -my $handles = { - VkInstance => { - objecttype => 'VK_OBJECT_TYPE_INSTANCE', - }, - VkPhysicalDevice => { - objecttype => 'VK_OBJECT_TYPE_PHYSICAL_DEVICE', - }, - VkDevice => { - objecttype => 'VK_OBJECT_TYPE_DEVICE', - }, - VkQueue => { - objecttype => 'VK_OBJECT_TYPE_QUEUE', - }, - VkCommandBuffer => { - objecttype => 'VK_OBJECT_TYPE_COMMAND_BUFFER', - }, -}; +use vulkan; + +use code; +use method; + +use Carp; + +my $registry; + +sub init { + my $api = shift; + + $registry = new vulkan(); + + # pre-load 'handle' types which aren't exported by the header parser (anonymous struct typedefs) + foreach my $r (grep { $_->{category} eq 'handle' } values %{$registry->{handles}}) { + die if $registry->{alias}->{"type:$r->{name}"}; + + print "Add vkhandle: $r->{name} ($r->{type})\n"; + my $s = { + name => "$r->{name}_T", + rename => $r->{name}, + type => 'struct', + size => 0, + items => [], + output => 1, + vkregistry => $r, + }; + + if ($r->{name} eq 'VkInstance') { + $s->{'struct:template'} = 'code:class=handle'; + } elsif ($r->{type} eq 'VK_DEFINE_HANDLE') { + $s->{'struct:template'} = 'code:vulkan=handle-dynamic'; + } + + $r->{vkheader} = $s; + $api->{data}->{"struct:$r->{name}_T"} = $s; + } + + # link api to registry + my @delete = (); + foreach my $s (values %{$api->{data}}) { + my $r = $registry->{index}->{"type:$s->{name}"}; + + if ($registry->{alias}->{"type:$s->{name}"} && !($s->{name} =~ m/_T$/)) { + # WTF will this break though + print "Suppress alias: $s->{name}\n"; + $s->{output} = 0; + push @delete, "$s->{type}:$s->{name}"; + } else { + $s->{vkregistry} = $r if defined $r; + $r->{vkheader} = $s if defined $r; + } + } + + map { delete $api->{data}->{$_} } @delete; + + 1; +} + +sub postprocess { + my $api = shift; + + analyseFunctions($api, $registry); + analyseInOut($api, $registry); + analyseStructs($api, $registry); + + 1; +} + + +# analyse functions and structs +# - try to guess which types are in-out/in-only/out-only (not very reliable) +# - determine which types can be used as arrays +# - mark function parameters which are arrays +sub analyseInOut { + my $api = shift; + my $registry = shift; + my $output = {}; + my $arrays = {}; + + foreach my $c (grep { $_->{category} =~ m/command|struct|union/ && defined $_->{vkheader} } values %{$registry->{data}}) { + my $d = $c->{vkheader}; + my $dindex = {}; + + map { $dindex->{$_->{name}} = $_ } @{$d->{items}}; + + # Check parameters -> const * are out-only, any with 'len' need array accessors + foreach my $m (@{$c->{items}}) { + my $r = $registry->{index}->{"type:$m->{baseType}"}; + my $n = $dindex->{$m->{name}}; + + die if !defined $n; + next if !defined $r; + + if ($r->{category} =~ m/struct|union/) { + $output->{$r->{name}} = $r if !($m->{fullType} =~ m/^const.*\*$/); + $arrays->{$r->{name}} = $r if $m->{len} || $m->{altlen} || $n->{deref} =~ m/^\[\d+/; + + # && $m->{fullType} =~ tr/*/*/ == 1; + } elsif ($r->{category} eq 'handle') { + $n->{array} = 1 if ($m->{len} || $m->{altlen}); + } + } + } + + foreach my $fuckoff ('VkBaseInStructure', 'VkBaseOutStructure') { + delete $arrays->{$fuckoff}; + delete $output->{$fuckoff}; + } + + my $out = { + 'VkDebugUtilsMessengerCallbackDataEXT' => $registry->{index}->{'type:VkDebugUtilsMessengerCallbackDataEXT'}, + }; + my $inout = { + 'VkComputePipelineCreateInfo' => $registry->{index}->{'type:VkComputePipelineCreateInfo'}, + }; + my $in = {}; + + foreach my $r (grep { $_->{category} =~ m/struct|union/on && defined $_->{vkheader} && !defined $out->{$_->{name}} && !defined $inout->{$_->{name}} } values %{$registry->{data}}) { + if ($r->{returnedonly}) { + $out->{$r->{name}} = $r; + } else { + $in->{$r->{name}} = $r; + } + } + + foreach my $r (values %{$output}) { + print "check $r->{name}\n"; + if ($r->{returnedonly} ne 'true') { + my $x = {}; + analyseInOutStruct($registry, $x, $r->{name}); + foreach my $s (values %{$x}) { + print "moved in -> in-out $s->{name} due to $r->{name}\n"; + delete $in->{$s->{name}}; + $inout->{$s->{name}} = $s; + } + + } + } + + my $set = sub { + my $r = shift; + my $rw = shift; + my $s = $r->{vkheader}; + + die "Missing vkheader ".Dumper($r) if !defined $s; + + $rw = $rw."i" if $arrays->{$r->{name}}; + + $s->{vkaccess}= $rw; + $s->{access} = $rw; + $s->{"$s->{type}:template"} = 'code:class=struct-array' if $arrays->{$r->{name}}; + }; + + print join '', map { "in-out: $_->{name}".($arrays->{$_->{name}} ? " array\n":"\n") } values %{$inout}; + print join '', map { "in : $_->{name}".($arrays->{$_->{name}} ? " array\n":"\n") } values %{$in}; + print join '', map { " out: $_->{name}".($arrays->{$_->{name}} ? " array\n":"\n") } values %{$out}; + + map { $set->($_, 'rw') } values %{$inout}; + map { $set->($_, 'w') } values %{$in}; + map { $set->($_, 'r') } values %{$out}; +} + +# dump all types in this type recursively into %{$record} +sub analyseInOutStruct { + my $registry = shift; + my $record = shift; + my $baseType = shift; + my $s = $registry->{index}->{"type:$baseType"}; + + #if (!defined $s) { + # print join "\n", sort(map { $_->{name} } values %{$registry->{data}}); + #} + die "no match for $baseType\n" if !defined $s; + + if (defined $s && !defined $s->{structextends} && $s->{category} =~ m/struct|union/n) { + $record->{$baseType} = $s; + + foreach my $m (@{$s->{items}}) { + analyseInOutStruct($registry, $record, $m->{baseType}); + } + } + if (defined $s && defined $s->{structextends}) { + print "extends? $baseType $s->{structextends}\n"; + } + +} + +# analyse functions +# - configure member functions +# - configure constructors +sub analyseFunctions { + my $api = shift; + my $registry = shift; + + # TODO: arrays etc + + # Scan functions + foreach my $c (grep { $_->{category} eq 'command' && defined $_->{vkheader} } values %{$registry->{data}}) { + my $d = $c->{vkheader}; + + # set extensions functions to use a different template + if ($c->{extensions}) { + $d->{extensions} = $c->{extensions}; + $d->{'init:template'} = $d->{items}->[0]->{deref} eq 'u64:${VkInstance_T}' + ? 'code:vulkan=invoke-dynamic-init-instance' : 'code:vulkan=invoke-dynamic-init'; + $d->{'func:template'} = 'code:vulkan=invoke-dynamic'; + } + + # look for constructors and member functions + my $first = $d->{items}->[0]; + my $last = $d->{items}->[$#{$d->{items}}]; + + my $ffirst = $c->{items}->[0]; + my $flast = $c->{items}->[$#{$c->{items}}]; + my $rfirst = $registry->{index}->{"type:$ffirst->{baseType}"}; + my $rlast = $registry->{index}->{"type:$flast->{baseType}"}; + + my $isstatic = ($rfirst->{type} ne 'VK_DEFINE_HANDLE') + && ($rfirst->{fullType} =~ tr/*/*/ == 0); + + my $iscreate = ($flast->{fullType} =~ tr/*/*/==1) + && !($flast->{fullType} =~ m/const/) + && !($flast->{len}) + && ($rlast->{type} =~ m/^(VK_DEFINE_HANDLE|VK_DEFINE_NON_DISPATCHABLE_HANDLE)$/n); + + $d->{vkiscreate} = $iscreate; + $d->{vkisstatic} = $isstatic; + + if ($iscreate && $isstatic) { + # fuck, all this work for vkCreateInstance() + $last->{output} = 0; + $last->{return} = 1; + $d->{return} = $last; + $d->{scope} = $last->{scope} = 'explicit'; # blah, for now + $last->{select}->{vkinstance} = 1 if ($flast->{baseType} ne 'VkInstance'); + + print "xxxa static $flast->{baseType}.$c->{name} $last->{deref} $rlast->{type} ".join("",split(/\n/,Dumper($flast)))."\n"; + } elsif ($ffirst->{fullType} eq $ffirst->{baseType} + && $rfirst->{type} eq 'VK_DEFINE_HANDLE') { + # member functions + $first->{output} = 0; + $first->{instance} = 1; + $d->{static} = 0; + + # member create function + if ($iscreate) { + $last->{output} = 0; + $last->{return} = 1; + $d->{return} = $last; + $d->{scope} = $last->{scope} = 'explicit'; + $last->{select}->{vkinstance} = 1 if ($flast->{baseType} ne 'VkInstance' && $rlast->{type} eq 'VK_DEFINE_HANDLE'); + + print "xxxb $ffirst->{baseType}.$c->{name} $last->{deref} $rlast->{type} ".join("",split(/\n/,Dumper($flast)))."\n"; + } + } + + if ($flast->{len} && $flast->{fullType} =~ tr/*/*/ == 1 + #&& $rlast->{type} eq 'VK_DEFINE_HANDLE') + ) { + $last->{array} = 1; + } + + # Link in successcodes (failure codes?) + if (defined($c->{successcodes})) { + $d->{success} = $d->{result}; + $d->{success}->{success} = join ',', map { "VkConstants.$_" } split /,/,$c->{successcodes}; + $d->{result}->{output} = 0; + + # output return value if there is more than 1 success code + if ($c->{successcodes} =~ tr/,/,/ >= 1 && !defined($d->{return})) { + $d->{result}->{output} = 1; + } + } + } + +# die; + +} +# analyse structures +# - which fields should be included in a set-all constructor +# - auto handling of arrays-with-length +# - handling of String[] +# - access modes for members +# - set-all constructor format hooks +sub analyseStructs { + my $api = shift; + my $registry = shift; + + foreach my $r (grep { $_->{category} =~ m/struct|union/ && defined $_->{vkheader}} values %{$registry->{data}}) { + my $s = $r->{vkheader}; + my $index = {}; + + map { $index->{$_->{name}} = $_ } @{$s->{items}}; + + # # Find out which fields should be included in a 'set-all' constructor + # for my $m (@{$s->{items}}) { + # if ($m->{deref} =~ m/^\$\{(.*)\}$/ && defined($api->{data}->{"struct:$1"})) { + # my $embed = $api->{data}->{"struct:$1"}; + # # TODO: need to work out how to set the fields first + # #if ($#{$embed->{items}} < 6) { + # # $m->{'set-all-struct'} = "struct:$1"; + # #} + # # $m->{vkset} = { type => $1 } ? + # } elsif ($m->{deref} =~ m/\[(\d+)([uif]\d+)\]/) { + # # 1D primitive array + # if ($1 <= 16) { + # $m->{vkset} = { select => { vkarray => 1 } }; + # } + # } elsif ($m->{deref} =~ m/\[(\d+)\[(\d+)([uif]\d+)\]\]/) { + # # 2D primitive array + # if (($1 * $2) <= 16) { + # $m->{vkset} = { select => { vkarray => 1 } }; + # } + # } else { + # # pointers or other primitives, probably + # $m->{vkset} = { }; + # } + # } + + # Special handling for types with 'len' specified + # TODO: not sure if i want this in general or just set-all constructor + for my $x (@{$r->{items}}) { + my $m = $index->{$x->{name}}; + + # if there's a len specified but ignore if set in .api file + if ($x->{len} && !($m->{array} || $m->{'array-size'} || $m->{'array-size-source'})) { + my @len = split ',', $x->{len}; + + if ($#len > 0 && $len[1] eq 'null-terminated') { + # String[], probably + my $size = $index->{$len[0]}; + + # for string[] + $size->{implied} = "Memory.length($m->{name})"; + $m->{select}->{vkstring} = 1; + } elsif ($len[0] =~ m/^[a-zA-Z]+$/) { + # Simple length referencing a parameter/field + my $size = $index->{$len[0]}; + + $size->{'array-size-source'} = $m; + #$size->{output} = 0; + $m->{'array-size'} = $size; + #$m->{select}->{vkstring} = 1; + $size->{select}->{'array-size-source'} = 1; + $m->{select}->{'array-size'} = 1; + + # need to know what type it is to get correct length, array, struct[], etc + $size->{implied} = "Memory.length($m->{name})"; + #$size->{implied} = "$size->{name}"; + + # use HandleArray<> rather than PointerArray<> for handles + if ($m->{deref} =~ m/^u64:u64:\$\{(.*)\}/) { + my $t = $api->{data}->{"struct:$1"}; die "no type $1" if !$t; + my $r = $t->{vkregistry}; + + $m->{array} = 1 if ($r->{type} =~ m/^(VK_DEFINE_HANDLE|VK_DEFINE_NON_DISPATCHABLE_HANDLE)$/n); + + $m->{select}->{array} = 1 if ($r->{type} =~ m/^(VK_DEFINE_HANDLE|VK_DEFINE_NON_DISPATCHABLE_HANDLE)$/n); + } + } + } + } + + # setup accessor mode (if not already set), and format hook for the set-all constructor + if ($r->{returnedonly} eq 'true') { + $s->{access} = 'r' if !$s->{vkaccess}; + map { $_->{access} = 'r' } @{$s->{items}}; + } elsif ($#{$r->{items}} >= 0 && $r->{items}->[0]->{baseType} eq 'VkStructureType') { + $s->{access} = 'w' if !$s->{vkaccess}; + push @{$s->{postformat}}, sub { formatConstructor(@_) }; + } else { + $s->{access} = 'rw' if !$s->{vkaccess}; + push @{$s->{postformat}}, sub { formatConstructor(@_) }; + } + # propagate access mode. + map { $_->{access} = $s->{access} } @{$s->{items}}; + + if ($s->{name} eq 'VkTransformMatrix') { + print "yyy ".Dumper($s); + die; + } + + } +} + +# TBD now in vkregistry.pm +sub findRegistryObject { + my $data = shift; + my $alias = shift; + my $func = shift; + + do { + my $f = $data->{$func}; + return $f if defined $f; + if ($func =~ m/^(.*)_T$/) { + $f = $data->{$1}; + return $f if defined $f; + } + #print "alias $func => $alias->{$func}\n"; + $func = $alias->{$func}; + } while ($func); + + return undef; +} + +# a basic struct, i.e. not a handle +sub matchStruct { + my $s = shift; + my $ctx = shift; + my $r = $s->{vkregistry}; + + return defined($r) + && $r->{type} ne 'VK_DEFINE_NON_DISPATCHABLE_HANDLE' + && $r->{type} ne 'VK_DEFINE_HANDLE'; +} + +# TODO: info required for these is evaluated in analyse # find all functions where the first parameter is a pointer to a dispatchable handle type sub matchObjectFunction { + my $c = shift; my $s = shift; - my $ctx = shift; + my $items = $c->{items}; + my $r = $s->{vkregistry}; + + die "vkregistry field missing ".Dumper($s) if !defined($r); + + return $r->{type} eq 'VK_DEFINE_HANDLE' + #&& !$registry->{alias}->{"type:$s->{name}"} + && defined($items->[0]) + && $items->[0]->{deref} eq "u64:\${$s->{name}}"; +} + +# a static create function +sub matchCreateFunction { + my $c = shift; + my $s = shift; + my $items = $c->{items}; + my $r = $s->{vkregistry}; + + return ($c->{vkiscreate}) + && ($c->{vkisstatic}) + && ($items->[$#$items]->{deref} eq "u64:u64:\${$s->{name}}"); + + # class is a handle + # last argument is a **handle - return defined($handles->{"$ctx->{rename}"}) - && defined($s->{items}->[0]) - && $s->{items}->[0]->{deref} eq "u64:\${$ctx->{name}}"; + return $c->{name} =~ m/^vkCreate/ + && $r->{type} eq 'VK_DEFINE_HANDLE' + && defined($items->[$#$items]) + && $items->[$#$items]->{deref} eq "u64:u64:\${$s->{name}}"; } -sub dummy { - die; +sub renameField { + my $name = shift; + my $m = shift; + my $s = shift; + + if ($name eq 'pGeometries' && $s->{name} eq 'VkAccelerationStructureBuildGeometryInfoKHR') { + # small hack to fix rename conflict + $name = 'Geometries0'; + } elsif ($m->{deref} =~ m/^((u64:){1,})\$\{(?.*)\}$/) { + my $len = length($1)/4; + my $r = $registry->{index}->{"type:$+{type}"}; + + # strip leading 'p' for pointer, handles are void * so ignore one level + $len = $len - 1 if ($r->{type} =~ m/^(VK_DEFINE_HANDLE|VK_DEFINE_NON_DISPATCHABLE_HANDLE)$/n); + $name = substr $name, $len; + } elsif ($m->{deref} =~ m/^((u64:){1,})/) { + # strip leading 'p' for pointer + $name = substr $name, length($1)/4; + } + + # studly-caps + $name =~ s/(?:^|_)(.)/\U$1/g; + + return $name; +} + +# collect args for 'all' constructor +sub collectAllArgs { + my $api = shift; + my $items = shift; + my $s = shift; + my $prefix = shift; + my @members = @{$s->{items}}; + + # Drop type and next fields + if ($#members >= 1 && $members[0]->{name} eq 'sType' && $members[1]->{name} eq 'pNext') { + @members = @members[2 .. $#members]; + } + + #print "collect $s->{name}\n"; + foreach my $m (@members) { + my $n; + + if ($m->{deref} =~ m/^\$\{(.*)\}$/ && defined($api->{data}->{"struct:$1"})) { + my $embed = $api->{data}->{"struct:$1"}; + # TODO: need to work out how to set the fields first + } elsif ($m->{deref} =~ m/\[(\d+)([uif]\d+)\]/) { + # 1D primitive array + $n = { %{$m}, select => { vkarray => 1 } } if ($1 <= 16); + } elsif ($m->{deref} =~ m/\[(\d+)\[(\d+)([uif]\d+)\]\]/) { + # 2D primitive array + $n = { %{$m}, select => { vkarray => 1 } } if (($1 * $2) <= 16); + } elsif ($m->{'array-size-source'} || $m->{implied}) { + #$n = { %{$m}, output=>0, implied => "Memory.length($m->{'array-size-source'}->{name})" }; + $n = { %{$m}, output=>0 }; + } else { + # everything else + $n = { %{$m} }; + } + + if (defined($n)) { + if ($prefix) { + $n->{name} = $prefix ? $prefix.'$'.$m->{name} : $m->{name}; + $n->{subtype} = $s->{name}; + $n->{subname} = $m->{name}; + $n->{localname} = $prefix; + } + push @{$items}, $n; + } + } + + # if (defined $f->{vkset}) { + # my $a = { %{$f} }; + # if ($prefix) { + # $a->{name} = $prefix ? $prefix.'$'.$f->{name} : $f->{name}; + # $a->{subtype} = $s->{name}; + # $a->{subname} = $f->{name}; + # $a->{localname} = $prefix; + # } + # $a->{select} = $f->{vkset}->{select} if defined $f->{vkset}->{select}; + # push @{$items}, $a; + # } elsif ($f->{'set-all-struct'}) { + # die "unimplemented"; + # my $e = $api->{data}->{$f->{'set-all-struct'}}; + # collectAllArgs($api, $items, $e, $prefix ? $prefix.'$'.$f->{name} : $f->{name}); + # } + # } +} + +sub uniq { + my %seen; + return grep { !$seen{$_}++ } @_; +} + +sub format1Constructor { + my $api = shift; + my $obj = shift; + my $res = shift; + my $s = shift; + my $c = shift; + + my $info = new method($api, $c); + my $init = join "\n\t\t", map { + "MemorySegment $_\$segment = (MemorySegment)$_\$SH.invokeExact(self\$.segment);" + } uniq map { + $_->{field}->{localname} ? $_->{field}->{localname} : () + } @{$info->{arguments}}; + + my $setAll = join ";\n\t\t", map { + my $v = { %{$_->{match}}, value => $_->{field}->{name} }; + if (defined ($_->{field}->{subtype})) { + $v->{name} = $_->{field}->{subtype}.'.'.$_->{field}->{subname}; + $v->{segment} = $_->{field}->{localname}.'$segment'; + } else { + $v->{segment} = 'self$.segment'; + } + # hack for string[] + $v->{tonative} = "({type})$_->{field}->{implied}" if $_->{field}->{implied}; + code::formatTemplate($_->{match}->{setnative}, $v); + } @{$info->{arguments}}; + $setAll .= ";"; + + my $template = $api->findTemplateName('code:vulkan=create-all'); + my $code; + + if (0) { + foreach my $l (split /\n/,Dumper($info)) { + $code .= "//$l\n"; + } + } + + $code .= code::applyTemplate($template, { %{$info->{vars}}, 'set-all' => $setAll, 'set-init' => $init}); + + push @{$res->{func}}, $code; +} + +# Create an 'all-set' constructor +# This creates a pseudo-function and formats that +# This also sets up the code to initialise sType +sub formatConstructor { + my $api = shift; + my $obj = shift; + my $res = shift; + my $s = shift; + my $r = $s->{vkregistry}; + + return if $s->{name} =~ m/^(VkBaseInStructure|VkBaseOutStructure)$/on; + + if ($#{$r->{items}} >= 0 && $r->{items}->[0]->{baseType} eq 'VkStructureType') { + push @{$res->{init}}, "// FIXME: array init?\n$r->{items}->[0]->{name}\$VH.set(segment, VkConstants.$r->{items}->[0]->{values});"; + } + + if ($s->{type} eq 'union') { + foreach my $u (grep { defined($_->{vkset}) } @{$s->{items}}) { + my $args = []; + + if ($u->{vkset}->{struct}) { + die "unimplemented"; + } else { + my $a = { %{$u} }; + $a->{select} = $u->{vkset}->{select} if defined $u->{vkset}->{select}; + push @{$args}, $a; + } + my $c = { + result => { + type => "$s->{type}:$s->{name}", + deref => "u64:\$\{$s->{name}\}", + name => 'result$', + output => 1, + }, + items => $args, + rename => $u->{rename}, + }; + format1Constructor($api, $obj, $res, $s, $c); + } + } else { + my $args = []; + + collectAllArgs($api, $args, $s, ""); + + if ($#{$args} >= 0) { + my $c = { + result => { + type => "$s->{type}:$s->{name}", + deref => "u64:\$\{$s->{name}\}", + name => 'result$', + output => 1, + }, + items => $args, + rename => "", + }; + format1Constructor($api, $obj, $res, $s, $c); + } + } } 1; diff --git a/src/notzed.vkheader/gen/vulkan.pm b/src/notzed.vkheader/gen/vulkan.pm new file mode 100644 index 0000000..cc5ec98 --- /dev/null +++ b/src/notzed.vkheader/gen/vulkan.pm @@ -0,0 +1,352 @@ + +# Routines for working with vulkan registry + +package vulkan; + +use strict; + +use Data::Dumper; +use XML::Parser; +use Time::HiRes qw(clock_gettime CLOCK_REALTIME); + +sub new { + my $class = shift; + + my $now = clock_gettime(CLOCK_REALTIME); + + my $xml = XML::Parser->new(Style => 'Tree'); + my $doc = $xml->parsefile('/usr/share/vulkan/registry/vk.xml') || die "unable to parse vulkan registry"; + + #print Dumper($doc); + + my $root = $doc->[1]; + my $roota = shift @{$root}; + + my $data = {}; + my $alias = {}; + my $extensions = {}; + + # This destructively consumes the whole tree so must be one pass + while ($#{$root} >= 0) { + my $xt = shift @{$root}; + my $xn = shift @{$root}; + + next if $xt eq '0'; + + my $xa = shift @{$xn}; + + if ($xt eq 'types') { + while ($#{$xn} >= 0) { + my $yt = shift @{$xn}; + my $yn = shift @{$xn}; + + next if $yt ne 'type'; + + my $ya = $yn->[0]; + + if ($ya->{category} =~ m/struct|union/) { + if (!defined($ya->{alias})) { + my $s = $ya; + + $s->{items} = []; + + shift @{$yn}; + while ($#{$yn} >= 0) { + my $mt = shift @{$yn}; + my $mm = shift @{$yn}; + + push @{$s->{items}}, loadMember($mm) if $mt eq 'member'; + } + + $data->{"type:$s->{name}"} = $s; + } else { + $alias->{"type:$ya->{name}"} = "type:$ya->{alias}"; + } + } elsif ($ya->{category} eq "handle") { + if (!defined($ya->{alias})) { + my $info = loadMember($yn); + my $s = $ya; + + $s->{name} = $info->{name}; + $s->{type} = $info->{baseType}; + + $data->{"type:$s->{name}"} = $s; + + # vulkan.h uses xx_T for handles + $alias->{"type:$s->{name}_T"} = "type:$s->{name}"; + } else { + $alias->{"type:$ya->{name}"} = "type:$ya->{alias}"; + } + } elsif ($ya->{category} =~ m/^(basetype|funcpointer|bitmask)$/n) { + if (!defined($ya->{alias})) { + my $info = loadMember($yn); + my $s = $ya; + + $s->{name} = $info->{name}, + $s->{type} => $info->{baseType} if defined $info->{baseType}; + + $data->{"type:$s->{name}"} = $s; + } else { + $alias->{"type:$ya->{name}"} = "type:$ya->{alias}"; + } + } elsif ($ya->{category} eq 'enum') { + $data->{"type:$ya->{name}"} = $ya; + } elsif ($ya->{requires} eq 'vk_platform') { + $ya->{category} = 'platform'; + $data->{"type:$ya->{name}"} = $ya; + } + } + } elsif ($xt eq 'enums') { + # TODO: load them all + if ($xa->{type} =~ m/enum|bitmask/o) { + $data->{"type:$xa->{name}"} = { category => $xa->{type}, name => $xa->{name} }; + } elsif ($xa->{name} eq 'API Constants') { + } + } elsif ($xt eq 'commands') { + while ($#{$xn} >= 0) { + my $yt = shift @{$xn}; + my $yn = shift @{$xn}; + + next if $yt ne 'command'; + + my $ya = shift @{$yn}; + + if (!defined($ya->{alias})) { + my $cmd = $ya; + + $cmd->{category} = 'command'; + $cmd->{items} = []; + $cmd->{proto} = {}; + + while ($#{$yn} >= 0) { + my $zt = shift @{$yn}; + my $zn = shift @{$yn}; + + if ($zt eq 'proto') { + $cmd->{proto} = loadMember($zn); + } elsif ($zt eq 'param') { + push @{$cmd->{items}}, loadMember($zn); + } + } + + my $name = $cmd->{proto}->{name}; + + # check we parsed it properly + if ($cmd->{proto}->{fullType} eq "") { + print Dumper([$ya, $yn]); + die(); + } + $cmd->{name} = $name; + + $data->{"type:$name"} = $cmd; + } else { + # want forward ref or not? + $alias->{"type:$ya->{name}"} = "type:$ya->{alias}"; + } + } + } elsif ($xt eq 'feature') { + my $feature = $xa; + + $feature->{require} = []; + + while ($#{$xn} >= 0) { + my $yt = shift @{$xn}; + my $yn = shift @{$xn}; + + next if $yt ne 'require'; + + push @{$feature->{require}}, loadRequire($data, $alias, $yn); + } + } elsif ($xt eq 'extensions') { + while ($#{$xn} >= 0) { + my $yt = shift @{$xn}; + my $yn = shift @{$xn}; + + next if $yt ne 'extension'; + + my $ext = shift @{$yn}; + + $ext->{require} = []; + + while ($#{$yn} >= 0) { + my $zt = shift @{$yn}; + my $zn = shift @{$yn}; + + next if $zt ne 'require'; + + push @{$ext->{require}}, loadRequire($data, $alias, $zn); + } + + $extensions->{$ext->{name}} = $ext; + } + } else { + #print "vulkan.pm: Ignore node: $xt\n"; + } + } + + # build various indices + my $index = {}; + + map { $index->{$_} = $data->{$_} } keys %{$data}; + map { $index->{$_} = findData($index, $alias, $_) } keys %{$alias}; + + my $handles = {}; + my $types = {}; + my $commands = {}; + + # these include aliases, should it? + foreach my $t (keys %{$index}) { + my $v = $data->{$t}; + $handles->{$v->{name}} = $v if $v->{category} eq 'handle'; + $types->{$v->{name}} = $v if $v->{category} =~ m/struct|union|platform/; + $commands->{$v->{name}} = $v if $v->{category} eq 'command'; + } + + # mark extension functions + foreach my $e (values %{$extensions}) { + foreach my $name (map { @{$_->{commands}} } @{$e->{require}}) { + my $r = $index->{"type:$name"}; + + die if !defined($r); + + push @{$r->{extensions}}, $e; + } + } + + # FIXME: link extensions up + # my $r = findData($data, $alias, "type:$ma->{name}"); + # die "cann't find $ma->{name}" if !defined $r; + # push @{$r->{extensions}}, $ext; + # push @{$r->{commands}}, $ma->{name}; + + my $self = { + data => $data, + alias => $alias, + index => $index, + extensions => $extensions, + handles => $handles, + types => $types, + commands => $commands, + }; + + $now = clock_gettime(CLOCK_REALTIME) - $now; + print "$now load registry\n"; + + bless $self, $class; + $self; +} + +# find an object including via alias +sub findData { + my $data = shift; + my $alias = shift; + my $name = shift; + + do { + my $s = $data->{$name}; + return $s if defined $s; + #print "alias $name => $alias->{$name}\n"; + $name = $alias->{$name}; + } while ($name); + + die "No match for type '$name'"; +} + +sub loadMember { + my $nn = shift; + #my $x = (join '',split('\n',Dumper($nn))); $x =~ s/ +/ /g; print "load: $x\n"; + my $m = shift @{$nn}; + my $baseType = ""; + my $fullType = ""; + my $name = ""; + + while ($#{$nn} >= 0) { + my $pt = shift @{$nn}; + my $pn = shift @{$nn}; + + if ($pt eq '0') { + $fullType .= $pn; + } elsif ($pt eq 'type') { + die if $pn->[1] != 0; + $baseType = $pn->[2]; + $fullType .= $baseType; + } elsif ($pt eq 'name') { + die if $pn->[1] != 0; + $name = $pn->[2]; + } elsif ($pt eq 'enum') { + die if $pn->[1] != 0; + $fullType .= $pn->[2]; + } + } + + # canonicalise spaces in c type + $fullType =~ s/(?{name} = $name; + $m->{baseType} = $baseType; + $m->{fullType} = $fullType; + + $m; +} + +sub loadRequire { + my $data = shift; + my $alias = shift; + my $nn = shift; + my $r = shift @{$nn}; + + $r->{enums} = []; + $r->{types} = []; + $r->{commands} = []; + + while ($#{$nn} >= 0) { + my $mt = shift @{$nn}; + my $mn = shift @{$nn}; + + if ($mt eq 'type') { + my $ma = shift @{$mn}; + push @{$r->{types}}, $ma->{name}; + } elsif ($mt eq 'command') { + my $ma = shift @{$mn}; + push @{$r->{commands}}, $ma->{name}; + } elsif ($mt eq 'enum') { + my $ma = shift @{$mn}; + push @{$r->{enums}}, $ma; + } + } + + $r; +} + +sub findElements { + my $n = shift; + my $name = shift; + my @list; + + while ($#{$n} >= 0) { + my $tag = shift @{$n}; + my $con = shift @{$n}; + + if ($tag eq $name) { + push @list, [$tag, $con]; + } + } + @list; +} + +sub scanElements { + my $n = shift; + + while ($#{$n} >= 0) { + my $tag = shift @{$n}; + my $con = shift @{$n}; + + print "$#{$n} "; + print "tag $tag\n"; + } +} + +1; diff --git a/src/notzed.vkregistry.test/classes/module-info.java b/src/notzed.vkregistry.test/classes/module-info.java new file mode 100644 index 0000000..7d1e258 --- /dev/null +++ b/src/notzed.vkregistry.test/classes/module-info.java @@ -0,0 +1,7 @@ + +module notzed.vkregistry.test { + requires notzed.vkregistry; + requires notzed.xlib; + + requires java.desktop; +} diff --git a/src/notzed.vkregistry/classes/vulkan/test/Cube.java b/src/notzed.vkregistry.test/classes/vulkan/test/Cube.java similarity index 100% rename from src/notzed.vkregistry/classes/vulkan/test/Cube.java rename to src/notzed.vkregistry.test/classes/vulkan/test/Cube.java diff --git a/src/notzed.vkregistry/classes/vulkan/test/GLMaths.java b/src/notzed.vkregistry.test/classes/vulkan/test/GLMaths.java similarity index 100% rename from src/notzed.vkregistry/classes/vulkan/test/GLMaths.java rename to src/notzed.vkregistry.test/classes/vulkan/test/GLMaths.java diff --git a/src/notzed.vkregistry/classes/vulkan/test/TestCube.java b/src/notzed.vkregistry.test/classes/vulkan/test/TestCube.java similarity index 100% rename from src/notzed.vkregistry/classes/vulkan/test/TestCube.java rename to src/notzed.vkregistry.test/classes/vulkan/test/TestCube.java diff --git a/src/notzed.vkregistry/classes/vulkan/test/TestMandelbrot.java b/src/notzed.vkregistry.test/classes/vulkan/test/TestMandelbrot.java similarity index 100% rename from src/notzed.vkregistry/classes/vulkan/test/TestMandelbrot.java rename to src/notzed.vkregistry.test/classes/vulkan/test/TestMandelbrot.java diff --git a/src/notzed.vkregistry.test/gen/cube.frag b/src/notzed.vkregistry.test/gen/cube.frag new file mode 100644 index 0000000..de24544 --- /dev/null +++ b/src/notzed.vkregistry.test/gen/cube.frag @@ -0,0 +1,8 @@ +#version 400 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable +layout (location = 0) in vec4 color; +layout (location = 0) out vec4 outColor; +void main() { + outColor = color; +} diff --git a/src/notzed.vkregistry.test/gen/cube.vert b/src/notzed.vkregistry.test/gen/cube.vert new file mode 100644 index 0000000..5d21e1e --- /dev/null +++ b/src/notzed.vkregistry.test/gen/cube.vert @@ -0,0 +1,14 @@ +#version 400 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable +layout (std140, binding = 0) uniform bufferVals { + mat4 mvp; +} data; +layout (location = 0) in vec4 pos; +layout (location = 1) in vec4 inColor; +layout (location = 0) out vec4 outColor; + +void main() { + outColor = inColor; + gl_Position = data.mvp * pos; +} diff --git a/src/notzed.vkregistry.test/gen/gen.make b/src/notzed.vkregistry.test/gen/gen.make new file mode 100644 index 0000000..a9f07aa --- /dev/null +++ b/src/notzed.vkregistry.test/gen/gen.make @@ -0,0 +1,17 @@ + +bin/status/notzed.vkregistry.test.classes: \ + bin/modules/notzed.vkregistry.test/vulkan/test/mandelbrot.bin \ + bin/modules/notzed.vkregistry.test/vulkan/test/cube_vs.bin \ + bin/modules/notzed.vkregistry.test/vulkan/test/cube_fs.bin + +bin/modules/notzed.vkregistry.test/vulkan/test/mandelbrot.bin: src/notzed.vkregistry.test/gen/mandelbrot.comp + mkdir -p $(@D) + glslangValidator --target-env vulkan1.0 -V -o $@ $< + +bin/modules/notzed.vkregistry.test/vulkan/test/cube_vs.bin: src/notzed.vkregistry.test/gen/cube.vert + mkdir -p $(@D) + glslangValidator --target-env vulkan1.0 -V -o $@ $< + +bin/modules/notzed.vkregistry.test/vulkan/test/cube_fs.bin: src/notzed.vkregistry.test/gen/cube.frag + mkdir -p $(@D) + glslangValidator --target-env vulkan1.0 -V -o $@ $< diff --git a/src/notzed.vkregistry.test/gen/mandelbrot.comp b/src/notzed.vkregistry.test/gen/mandelbrot.comp new file mode 100644 index 0000000..6a45590 --- /dev/null +++ b/src/notzed.vkregistry.test/gen/mandelbrot.comp @@ -0,0 +1,60 @@ +#version 450 + +#define WIDTH (1920*1) +#define HEIGHT (1080*1) +#define LWS_X 8 +#define LWS_Y 8 +#define LIMIT 10000 + +layout (local_size_x = LWS_X, local_size_y = LWS_Y, local_size_z = 1 ) in; + +layout(std430, binding = 0) buffer buf { + uint imageData[]; +}; + +void main() { + + /* + In order to fit the work into workgroups, some unnecessary threads are launched. + We terminate those threads here. + */ + if(gl_GlobalInvocationID.x >= WIDTH || gl_GlobalInvocationID.y >= HEIGHT) + return; + + float x = float(gl_GlobalInvocationID.x) / float(WIDTH); + float y = float(gl_GlobalInvocationID.y) / float(HEIGHT); + + /* + What follows is code for rendering the mandelbrot set. + */ + vec2 uv = vec2(x, (y - 0.5) * (12.0 / 19.0) + 0.5); + float n = 0.0; + vec2 c = vec2(-.445, 0.0) + (uv - 0.5)*(4.0); + vec2 z = vec2(0.0); + const int M = LIMIT; + + for (int i = 0; i 4) + break; + n++; + } + + // we use a simple cosine palette to determine color: + // http://iquilezles.org/www/articles/palettes/palettes.htm + float t = float(n) * 500.0 / float(M); + vec3 d = vec3(0.5, 0.5, 0.5); + vec3 e = vec3(0.5, 0.5, 0.5); + vec3 f = vec3(1.0, 1.0, 1.0); + vec3 g = vec3(0.00, 0.33, 0.67); + + vec4 color = vec4( d + e*cos( 6.28318*(f*t+g) ) ,1.0); + + if (n == M) + color = vec4(0, 0, 0, 1); + + // store the rendered mandelbrot set into a storage buffer: + imageData[WIDTH * gl_GlobalInvocationID.y + gl_GlobalInvocationID.x] = packUnorm4x8(color); + //imageData[WIDTH * gl_GlobalInvocationID.y + gl_GlobalInvocationID.x].value = color; +} diff --git a/src/notzed.vkregistry/classes/module-info.java b/src/notzed.vkregistry/classes/module-info.java index b8ea86d..fa090a5 100644 --- a/src/notzed.vkregistry/classes/module-info.java +++ b/src/notzed.vkregistry/classes/module-info.java @@ -1,9 +1,6 @@ module notzed.vkregistry { requires transitive notzed.nativez; - requires transitive notzed.xlib; - - requires java.desktop; exports vulkan; } diff --git a/src/notzed.vkregistry/gen/gen.make b/src/notzed.vkregistry/gen/gen.make index e502e01..ff42ddd 100644 --- a/src/notzed.vkregistry/gen/gen.make +++ b/src/notzed.vkregistry/gen/gen.make @@ -1,25 +1,8 @@ -G=src/notzed.vkregistry/gen - bin/status/notzed.vkregistry.classes: \ - bin/status/notzed.vkregistry.export \ - bin/modules/notzed.vkregistry/vulkan/test/mandelbrot.bin \ - bin/modules/notzed.vkregistry/vulkan/test/cube_vs.bin \ - bin/modules/notzed.vkregistry/vulkan/test/cube_fs.bin + bin/status/notzed.vkregistry.export -bin/status/notzed.vkregistry.export: $(G)/export-vulkan $(G)/VkDevice-part.java $(G)/VkInstance-part.java - $(G)/export-vulkan -t vulkan -d bin/gen/notzed.vkregistry/classes +bin/status/notzed.vkregistry.export: src/notzed.vkregistry/gen/export-vulkan src/notzed.vkregistry/gen/VkDevice-part.java src/notzed.vkregistry/gen/VkInstance-part.java + src/notzed.vkregistry/gen/export-vulkan -t vulkan -d bin/gen/notzed.vkregistry/classes mkdir -p $(@D) touch $@ - -bin/modules/notzed.vkregistry/vulkan/test/mandelbrot.bin: $(G)/mandelbrot.comp - mkdir -p $(@D) - glslangValidator --target-env vulkan1.0 -V -o $@ $< - -bin/modules/notzed.vkregistry/vulkan/test/cube_vs.bin: $(G)/cube.vert - mkdir -p $(@D) - glslangValidator --target-env vulkan1.0 -V -o $@ $< - -bin/modules/notzed.vkregistry/vulkan/test/cube_fs.bin: $(G)/cube.frag - mkdir -p $(@D) - glslangValidator --target-env vulkan1.0 -V -o $@ $< -- 2.39.5