Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Image loading with stbi+gfx not working properly (Throwing unhandled exception) #16670

Open
MightyPancake opened this issue Dec 14, 2022 · 12 comments
Labels
Bug This tag is applied to issues which reports bugs. Status: Confirmed This bug has been confirmed to be valid by a contributor.

Comments

@MightyPancake
Copy link

Describe the bug

Adding an image with stbi in the following (correct?) way is not working in this version.
The issue is related to the size of the image/data given and expected to sokol.gfx.
Here's the image I tried to load (although, I tried a few others; all of them didn't work)
image
in

Expected Behavior

The image should load correctly.

Current Behavior

The program crashes on this function call with a valid image path.

Reproduction Steps

fn new_img(path string) ?Image {
	data := stbi.load(path) or {return err}
	pixels := &u8(data.data)
	println("Image loaded!\nWidth: $data.width\nHeight: $data.height\nChannels: $data.nr_channels\nExtension: $data.ext")
	mut img_desc := gfx.ImageDesc{
		width: data.width,
		height: data.height,
		num_mipmaps: 0,
		min_filter: .linear,
		mag_filter: .linear,
		label: "image".str
	}
	img_desc.data.subimage[0][0] = gfx.Range{
		ptr: pixels
		size: usize(data.width*data.height*data.nr_channels)
	}
	//size: usize(data.width*data.height*data.nr_channels)
	image := gfx.make_image(img_desc)
	return Image(image)
}

Possible Solution

I noticed that in the gg implementation it's stated that the value for size should be: 4 * width * height, however, doing so makes crashes the program. This is possibly due to Sokol trying to read bytes that are out of range of the pointer (array) returned by stbi.
A workaround for this might be just creating a more extensive array and pasting the content fetched from stbi while leaving the rest of the array zeroed. That's a waste of space, but it might work :)

Additional Information/Context

This code worked a couple of months ago (4?)

@spytheman On discord advised reverting my V locally to 6e24f7e, which fixed the issue, so something fishy probably happened after that commit.

V version

V 0.3.2 69f7c45

Environment details (OS name and version, etc.)

Windows 11

@MightyPancake MightyPancake added the Bug This tag is applied to issues which reports bugs. label Dec 14, 2022
@spytheman
Copy link
Member

spytheman commented Dec 14, 2022

I suggested git revert 6e24f7e which undoes specifically that commit, so I think the problem /difference in expectation between the user code and vlib is in it.

That commit corresponds to #16564

@larpon
Copy link
Contributor

larpon commented Dec 30, 2022

Probably also related #16754

We have situation were the amount of channels (nr_channels) multiplied by width * height returned by stbi works on some devices - and sometimes it doesn't...

@larpon
Copy link
Contributor

larpon commented Dec 30, 2022

If I'm not mistaken, img_desc.data.subimage[0][0] = gfx.Range{...}, the data passed in the gfx.Range should match the current sokol context's pixel format - and for gg (which used sokol_gl.h under the hood) that's IIRC SG_PIXELFORMAT_RGBA8 (that's why I argue that we should keep the 4 * width * height code as I reintroduced in #16564) we could try to either zero the pixel data array for 4 * width * height as @MightyPancake suggests or maybe try to zero the whole image description struct as sokol_gl.h is also doing.

@larpon
Copy link
Contributor

larpon commented Dec 30, 2022

More info: nothings/stb#335 (comment).

If the stbi folks are correct the data returned from stbi_load* functions will have the 4th channel set if it's passed STBI_rgb_alpha as desired_channels (req_comp) - which is exactly what we do in all stbi related load functions in gg since #16564 - so using nr_channels when nr_channels <= 3 should not work, exactly as the flappylearning example didn't work on my phone... I'm confused, but still believe that #16564 is doing the correct (according to sokol/stbi documentation) - so something else must be tripping us.

Also note the glPixelStorei(GL_UNPACK_ALIGNMENT, 1); suggestion by user nothings, right after the comment I linked above.
More on the alignment here: https://stackoverflow.com/a/11045278/1904615

@larpon
Copy link
Contributor

larpon commented Dec 30, 2022

@MightyPancake - what pixel format does your graphics pipeline use (the pipeline in which context you're loading the image)?
(It's hard for us to replicate the bug since we don't have the rest of your code)

@larpon
Copy link
Contributor

larpon commented Dec 30, 2022

On a, somewhat related, side note the attached image works fine in the image viewer on my machine
image

@spytheman
Copy link
Member

spytheman commented Dec 31, 2022

btw, https://github.com/nothings/stb/blob/master/stb_image.h is already on v2.27, it may be a good idea to update our copy as well. Unlike some other thirdparty libraries, they are usually very careful to not make breaking API changes, so it should be relatively painless.

edit: done in #16825

@JalonSolov
Copy link
Contributor

Is this still an issue with current V? Without a complete working example, I can't test...

@larpon
Copy link
Contributor

larpon commented Mar 26, 2023

Reports have been in the few and very unclear / hard to reproduce since #16564 - I think we can close this. At least until we have something concrete, code to test against

@its-popplewell
Copy link

I have once again run into this issue -- I am just trying to render a square with a texture on it and I am getting an assertion issue: Assertion failed: (_sg.valid), function sg_make_image, file sokol_gfx.h, line 17546. Terminated by signal 6 (SIGABRT).

It seems like my image is loading properly (as I am printing out the result):

stbi.Image{
    width: 640
    height: 360
    nr_channels: 4
    ok: true
    data: 10f436000
    ext: 'png'
}

Here is my code, it is largely adapted from the sokol examples.

// Copyright(C) 2022 Lars Pontoppidan. All rights reserved.
// Use of this source code is governed by an MIT license file distributed with this software package
module main

// Example shader triangle adapted to V from https://github.com/floooh/sokol-samples/blob/1f2ad36/sapp/triangle-sapp.c
import sokol.sapp
import sokol.gfx
import gg
import stbi

// Use `v shader` or `sokol-shdc` to generate the necessary `.h` file
// Using `v shader -v .` in this directory will show some additional
// info - and what you should include to make things work.
#include "@VMODROOT/simple_shader.h" # # It should be generated with `v shader .`

// simple_shader_desc is a C function declaration defined by
// the `@program` entry in the `simple_shader.glsl` shader file.
// When the shader is compiled this function name is generated
// by the shader compiler for easier inclusion of universal shader code
// in C (and V) code.
fn C.simple_shader_desc(gfx.Backend) &gfx.ShaderDesc

// Vertex_t makes it possible to model vertex buffer data
// for use with the shader system
struct Vertex_t {
	// Position
	x f32
	y f32
	z f32
	// Color
	r f32
	g f32
	b f32
	a f32
	// uv
	u f32
	v f32
}

//
//
//

fn create_texture(imgpath string) (gfx.Image, gfx.Sampler) {
	data := stbi.load(imgpath, stbi.LoadParams{}) or {
		println('Failed to load image')
		panic(err)
	}

	println(data)

	mut img_desc := gfx.ImageDesc{
		width: data.width
		height: data.height
		pixel_format: .rgba8
		num_mipmaps: 0
		// wrap_u: .clamp_to_edge
		// wrap_v: .clamp_to_edge
		// min_filter: .linear
		// max_filter: .linear
		// usage: .dynamic
		label: &u8(0)
		d3d11_texture: 0
	}

	//println(tymeof(data.data[0]))

	// comment if .dynamic is enabled
	img_desc.data.subimage[0][0] = gfx.Range{
		ptr: data.data
		size: usize(data.width * data.height * 4)
	}

	println(img_desc)

	sg_img := gfx.make_image(&img_desc)

	//println(sg_img)

	mut smp_desc := gfx.SamplerDesc{
		min_filter: .linear
		mag_filter: .linear
		wrap_u: .clamp_to_edge
		wrap_v: .clamp_to_edge
	}

	sg_smp := gfx.make_sampler(&smp_desc)
	return sg_img, sg_smp
}

fn destroy_texture(sg_img gfx.Image) {
	gfx.destroy_image(sg_img)
}

//
//
//

fn main() {
	mut app := &App{
		width: 800
		height: 400
		pass_action: gfx.create_clear_pass_action(0.0, 0.0, 0.0, 1.0) // This will create a black color as a default pass (window background color)
	}
	app.run()
}

struct App {
	pass_action gfx.PassAction
mut:
	width           int
	height          int
	texture         gfx.Image
	sampler         gfx.Sampler
	shader_pipeline gfx.Pipeline
	bind            gfx.Bindings
}

fn (mut a App) run() {
	title := 'V Simple Shader Example'
	desc := sapp.Desc{
		width: a.width
		height: a.height
		user_data: a
		init_userdata_cb: init
		frame_userdata_cb: frame
		window_title: title.str
		html5_canvas_name: title.str
		cleanup_userdata_cb: cleanup
		sample_count: 4 // Enables MSAA (Multisample anti-aliasing) x4 on rendered output, this can be omitted.
	}

	sapp.run(&desc)
}

fn init(user_data voidptr) {
	mut app := unsafe { &App(user_data) }
	mut desc := sapp.create_desc()

	app.texture, app.sampler = create_texture('testimg.png')

	gfx.setup(&desc)

	vertices := [
		Vertex_t{-0.5, 0.5, 0.5, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0},
		Vertex_t{0.5, -0.5, 0.5, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0},
		Vertex_t{-0.5, -0.5, 0.5, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0},
		Vertex_t{0.5, 0.5, 0.5, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0},
	]

	indices := [
		u16(0),
		3,
		1,
		0,
		1,
		2,
	]

	mut vertex_buffer_desc := gfx.BufferDesc{
		label: c'triangle-vertices'
	}
	unsafe { vmemset(&vertex_buffer_desc, 0, int(sizeof(vertex_buffer_desc))) }

	vertex_buffer_desc.size = usize(vertices.len * int(sizeof(Vertex_t)))
	vertex_buffer_desc.data = gfx.Range{
		ptr: vertices.data
		size: vertex_buffer_desc.size
	}

	app.bind.vertex_buffers[0] = gfx.make_buffer(&vertex_buffer_desc)

	mut index_buffer_desc := gfx.BufferDesc{
		label: c'triangle-indices'
	}
	unsafe { vmemset(&index_buffer_desc, 0, int(sizeof(index_buffer_desc))) }

	index_buffer_desc.size = usize(indices.len * int(sizeof(u16)))
	index_buffer_desc.@type = .indexbuffer
	index_buffer_desc.data = gfx.Range{
		ptr: indices.data
		size: index_buffer_desc.size
	}

	app.bind.index_buffer = gfx.make_buffer(&index_buffer_desc)
	app.bind.fs.images[C.SLOT_tex] = app.texture
	app.bind.fs.samplers[C.SLOT_smp] = app.sampler

	shader := gfx.make_shader(C.simple_shader_desc(gfx.query_backend()))

	mut pipeline_desc := gfx.PipelineDesc{}
	unsafe { vmemset(&pipeline_desc, 0, int(sizeof(pipeline_desc))) }

	pipeline_desc.shader = shader
	pipeline_desc.layout.attrs[C.ATTR_vs_position].format = .float3
	pipeline_desc.layout.attrs[C.ATTR_vs_color0].format = .float4
	pipeline_desc.layout.attrs[C.ATTR_vs_texcoord].format = .float2
	pipeline_desc.index_type = .uint16
	pipeline_desc.label = c'triangle-pipeline'

	app.shader_pipeline = gfx.make_pipeline(&pipeline_desc)
}

fn cleanup(user_data voidptr) {
	gfx.shutdown()
}

fn frame(user_data voidptr) {
	mut app := unsafe { &App(user_data) }

	pass := sapp.create_default_pass(app.pass_action)
	gfx.begin_pass(&pass)

	gfx.apply_pipeline(app.shader_pipeline)
	gfx.apply_bindings(&app.bind)

	gfx.draw(0, 6, 1)

	gfx.end_pass()
	gfx.commit()
}

and below is my shader code, which is also fairly basic.

// The following defines a vertex shader main function
@vs vs
in vec4 position;
in vec4 color0;
in vec2 texcoord;

out vec4 color;
out vec2 uv;

// You can add more functions here

void main() {
    gl_Position = position;
    color = color0;
    uv = texcoord;
}
@end

// The following defines a fragment shader main function
@fs fs
uniform texture2D tex;
uniform sampler smp;

in vec4 color;
in vec2 uv;
out vec4 frag_color;

// You can add more functions here

void main() {
    //frag_color = color;
    frag_color= texture(sampler2D(tex, smp), uv);
}
@end

// The value after `@program` and before `vs fs` decide a part of the name
// of the C function you need to define in V. The value entered is suffixed `_shader_desc`
// in the generated C code. Thus the name for this becomes: `simple_shader_desc`.
// In V it's signature then need to be defined as:
// `fn C.simple_shader_desc(gfx.Backend) &gfx.ShaderDesc`. See `simple_shader.v` for the define.
//
// Running `v shader -v .` in this dir will also show you brief information
// about how to use the compiled shader.
@program simple vs fs

There seem to be no resources to help with this and I am completely lost. Any help is appreciated.

@JalonSolov
Copy link
Contributor

Here's what I get when I try v -g run .. I see a window open very briefly (closes too quickly to see if anything was rendered), then see this in the console:

$ v -g run .
simple_shader.v:8:8: warning: module 'gg' is imported but never used
    6 | import sokol.sapp
    7 | import sokol.gfx
    8 | import gg
      |        ~~
    9 | import stbi
   10 |
Failed to load image
V panic: stbi_image failed to load from "testimg.png"
v hash: e9c9580
/tmp/v_1000/../../../../../../home/jalon/git/v/vlib/builtin/builtin.c.v:136: at _v_panic: Backtrace
/tmp/v_1000/../../../../../../tmp/simple_shader/simple_shader.v:47: by main__create_texture
/tmp/v_1000/../../../../../../tmp/simple_shader/simple_shader.v:140: by main__init
/home/jalon/git/v/thirdparty/sokol/sokol_app.h:3046: by _sapp_call_init
/home/jalon/git/v/thirdparty/sokol/sokol_app.h:3278: by _sapp_frame
/home/jalon/git/v/thirdparty/sokol/sokol_app.h:11598: by _sapp_linux_run
/home/jalon/git/v/thirdparty/sokol/sokol_app.h:11655: by sapp_run
/tmp/v_1000/../../../../../../home/jalon/git/v/vlib/sokol/sapp/sapp.c.v:238: by sokol__sapp__run
/tmp/v_1000/../../../../../../tmp/simple_shader/simple_shader.v:133: by main__App_run
/tmp/v_1000/../../../../../../tmp/simple_shader/simple_shader.v:105: by main__main
/tmp/v_1000/../../../../../../tmp/v_1000/simple_shader.01J5G6CVF96KXZ4ZEST4W6RTXJ.tmp.c:53700: by main
$

of course, that's because no image was supplied to test with.

So, I copied the image from the original comment, and saved it as testimg.png.

When I ran it again, I got this output in the console

$ v -g run .
simple_shader.v:8:8: warning: module 'gg' is imported but never used
    6 | import sokol.sapp
    7 | import sokol.gfx
    8 | import gg
      |        ~~
    9 | import stbi
   10 |
stbi.Image{
    width: 2000
    height: 2000
    nr_channels: 4
    ok: true
    data: &7d53d2000000
    ext: 'png'
}
sokol.gfx.ImageDesc(C.sg_image_desc{
    type: _default
    render_target: false
    width: 2000
    height: 2000
    num_slices: 0
    num_mipmaps: 0
    usage: _default
    pixel_format: rgba8
    sample_count: 0
    data:     sokol.gfx.ImageData(C.sg_image_data{
    subimage: [[C.sg_range{
    ptr: 7d53d2000000
    size: 16000000
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}], [C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}], [C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}], [C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}], [C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}], [C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}, C.sg_range{
    ptr: 0
    size: 0
}]]
})
    label: &C""
    gl_textures: [0, 0]
    gl_texture_target: 0
    mtl_textures: [0x0, 0x0]
    d3d11_texture: 0
    d3d11_shader_resource_view: 0
    wgpu_texture: 0
})
simple_shader: /home/jalon/git/v/thirdparty/sokol/sokol_gfx.h:18136: sg_make_image: Assertion `_sg.valid' failed.
7d53dbf9a3f4 : at ???: RUNTIME ERROR: abort() called
7d53dbf41120 : by ???
7d53dbf284c3 : by ???
7d53dbf283df : by ???
7d53dbf39177 : by ???
/home/jalon/git/v/thirdparty/sokol/sokol_gfx.h:18136: by sg_make_image
/tmp/v_1000/../../../../../../home/jalon/git/v/vlib/sokol/gfx/gfx.c.v:48: by sokol__gfx__make_image
/tmp/v_1000/../../../../../../tmp/simple_shader/simple_shader.v:76: by main__create_texture
/tmp/v_1000/../../../../../../tmp/simple_shader/simple_shader.v:140: by main__init
/home/jalon/git/v/thirdparty/sokol/sokol_app.h:3046: by _sapp_call_init
/home/jalon/git/v/thirdparty/sokol/sokol_app.h:3278: by _sapp_frame
/home/jalon/git/v/thirdparty/sokol/sokol_app.h:11598: by _sapp_linux_run
/home/jalon/git/v/thirdparty/sokol/sokol_app.h:11655: by sapp_run
/tmp/v_1000/../../../../../../home/jalon/git/v/vlib/sokol/sapp/sapp.c.v:238: by sokol__sapp__run
/tmp/v_1000/../../../../../../tmp/simple_shader/simple_shader.v:133: by main__App_run
/tmp/v_1000/../../../../../../tmp/simple_shader/simple_shader.v:105: by main__main
/tmp/v_1000/../../../../../../tmp/v_1000/simple_shader.01J5G6QVX548D06A9DAQZ3AY9W.tmp.c:53700: by main
$

@JalonSolov JalonSolov reopened this Aug 17, 2024
@JalonSolov JalonSolov added the Status: Confirmed This bug has been confirmed to be valid by a contributor. label Aug 17, 2024
@larpon
Copy link
Contributor

larpon commented Aug 18, 2024

This should be it's own fresh issue, not a reopening IMO

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug This tag is applied to issues which reports bugs. Status: Confirmed This bug has been confirmed to be valid by a contributor.
Projects
None yet
Development

No branches or pull requests

5 participants