kthakore has asked for the wisdom of the Perl Monks concerning the following question:

In an attempt to make pixel access easier on SDL_Surface pixels. I have started work on SDLx::Surface. So far I have only start on the 32 bpp surfaces.

The general idea is to make Pointer Values (PV) of each pixel in the surface and place them into a 2D matrix. First I make pointer values like this:

SV * get_pixel32 (SDL_Surface *surface, int x, int y) { //Convert the pixels to 32 bit Uint32 *pixels = (Uint32 *)surface->pixels; //Get the requested pixel Uint32* u_ptr = pixels + ( y * surface->w ) + x ; SV* sv = newSVpv("a",1); //Make a temp SV* value on the go SvCUR_set(sv, sizeof(Uint32)); //Specify the new CUR length SvLEN_set(sv, sizeof(Uint32)); //Specify the LEN length SvPV_set(sv,(char*)u_ptr); // set the actual pixel's pointer a +s the memory space to use return sv; //make a modifiable reference using u_ptr's place as th +e memory :) }

Next I loop through all the pixels and put them in a 2D array format, shown below:

AV * construct_p_matrix ( SDL_Surface *surface ) { AV * matrix = newAV(); int i, j; for( i =0 ; i < surface->w; i++) { AV * matrix_row = newAV(); for( j =0 ; j < surface->h; j++) { av_push(matrix_row, get_pixel32(surface, i,j) ); } av_push(matrix, newRV_noinc((SV*) matrix_row) ); } return matrix; }

You can see the complete here.

In Perl I can do a get access on this pixel using:

my $surf32_matrix = SDLx::Surface::pixel_array($screen_surface); print unpack 'b*', $surf32_matrix->[0][0]; # pixel value at x = 0 a +nd y =0 #OUTPUT: # 11111111000000000000000000000000

The structure of the PV is using Devel::Peek is :

print Dump $surf32_matrix->[0][0]; #OUTPUT: #SV = PV(0xed0dbc) at 0xeb5344 # REFCNT = 1 # FLAGS = (POK,pPOK) # PV = 0x9e04ac "\0\0\377\0" # CUR = 4 # LEN = 4

The problem is in setting the value of this pointer value. I have tried the following things with no success:

if ( SDL::Video::MUSTLOCK($screen_surface) ) { return if ( SDL::Video::lock_surface($screen_surface) < 0 ); #requ +ired for pixel operations } #USING pack my $green = pack 'b*', '11111111000000000000000000000000'; substr( $surf32_matrix->[0][0], 0, 8 * 4, $green); #no change #substr( $surf32_matrix->[0][0], 0, 8 * 4, 0xFF000000); segfault substr( ${$surf32_matrix->[0][0]}, 0, 8 * 4, 0xFF000000); #no change #$surf32_matrix->[0][0] = $green; SEGFAULT's cannot write to memory ${$surf32_matrix->[0][0]} = $green; #no change SDL::Video::unlock_surface($screen_surface) if ( SDL::Video::MUSTLOCK($screen_surface) );

You can see an example here.

Any help will be greatly appreciated.

Replies are listed 'Best First'.
Re: Writing to Pointer Values (PV) in XS
by ikegami (Patriarch) on Jun 23, 2010 at 17:55 UTC

    First I make pointer values like this

    If SvLEN is non-zero, the PV belongs to Perl, and Perl will free it. This could be the cause of the segfault you had. You should set SvLEN to zero.

    SV* sv = newSV_type(SVt_PV); SvPV_set(sv, (char*)u_ptr); SvLEN_set(sv, 0); /* So Perl won't free it. */ SvCUR_set(sv, sizeof(Uint32)); SvPOK_on(sv);

    That way, if Perl reallocates the PV or if the scalar goes out of scope, it won't free the existing PV first. It's up to you to free surface->pixels.

    AV * surfacex_pixel_array ( surface )

    Perl functions can't return anything but a list of scalar. Specifically, you can't return an array. You need to return a reference to the array, or you need to flatten the array.

    SV * construct_p_matrix ( SDL_Surface *surface ) { ... return newRV_noinc(matrix); }

    The problem is in setting the value of this pointer value.

    use strict; use warnings; use Inline C => <<'__EOI__'; /* Something Perl shouldn't free */ static char s[4] = "\x11\x11\x11\x11"; SV* fetch() { SV* sv = newSV_type(SVt_PV); SvPV_set(sv, s); SvPOK_on(sv); SvLEN_set(sv, 0); SvCUR_set(sv, 4); return newRV_noinc(sv); } __EOI__ my $ref = fetch(); print(unpack('H*', $$ref), "\n"); $$ref = pack('N', 0x22222222); # Bad: Replaces the $ref = fetch(); # PV instead of print(unpack('H*', $$ref), "\n"); # modifying it. substr($$ref, 0, 4, pack('N', 0x33333333)); # OK $ref = fetch(); print(unpack('H*', $$ref), "\n"); substr($$ref, 0, 4) = pack('N', 0x44444444); # OK $ref = fetch(); print(unpack('H*', $$ref), "\n"); substr($$ref, 0) = pack('N', 0x55555555); # OK $ref = fetch(); print(unpack('H*', $$ref), "\n"); vec($$ref, 0, 32) = 0x66666666; # OK $ref = fetch(); print(unpack('H*', $$ref), "\n");
    11111111 11111111 33333333 44444444 55555555 66666666

    I'm using a reference because my $sv = f(); would copy the string if I returned the scalar directly. You don't need to place refs in your array since the array acts as the reference. For example, the following should work if the array is setup properly:

    vec($surf32_matrix->[0][0], 0, 32) = 0x12345678;

    That said, your approach seems very fragile.

      Hi! So I have news on this implementation.

      I would like to put a note here.

      HUGE SUCCESS!

      You can see this implementation on our repo. The relevant files are the XS and the example script .

      What are the fragile issues that you can see? What more can I do ensure memory handling is done correctly

        You can see this implementation on our repo.

        I'm not going to review your code.

        newSV_type is a new addition to the core. If you want to support older Perls, you'll have to use something else, or include ppport (which isn't hard, and will handle other such problems if any). I chose newSV_type because it doesn't create a string buffer that will end up being freed.

        What are the fragile issues that you can see?

        I was referring to how easy it is to accidentally replace the PV. If it's a private interface between your XS and Perl components, no problem. If it's a public interface, it's a problem.

        What more can I do ensure memory handling is done correctly

        Test your code using valgrind.

      Moreover when you do:

      /* Something Perl shouldn't free */ static char s[4] = "\x11\x11\x11\x11"; SV* fetch() { SV* sv = newSV_type(SVt_PV); SvPV_set(sv, s); SvPOK_on(sv); SvLEN_set(sv, 0); SvCUR_set(sv, 4); return newRV_noinc(sv); }

      The Devel::Peek::Dump shows:

      SV = IV(0x8add89c) at 0x8add8a0 REFCNT = 1 FLAGS = (PADMY,ROK) RV = 0x8abf520 SV = PV(0x8abc700) at 0x8abf520 REFCNT = 1 FLAGS = (POK,pPOK) PV = 0xb7d0e06c "\21\21\21\21" CUR = 4 LEN = 0

      However for my set of

      SV * sv = newSV_type(SVt_PV); SvPV_set(sv, surface->pixels); SvPOK_on(sv); SvLEN_set(sv, 0); SvCUR_set(sv, surface->format->BytesPerPixel * surface->w * +surface->h); RETVAL = newRV_noinc(sv);

      The surface Devel::Peek::Dump has different types:

      SV = RV(0x142b330) at 0x142b324 REFCNT = 1 FLAGS = (TEMP,ROK) RV = 0x3e90fc SV = PV(0x1287a9c) at 0x3e90fc REFCNT = 1 FLAGS = (POK,pPOK) PV = 0xcea408 "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" CUR = 16 LEN = 0

      Why is the type a RV and not IV?

        What's important is that it's ROK, and it is. The RV type doesn't even exist anymore because it got merged with IV type, so don't worry about it.