Skip to content

Commit ef5d8d6

Browse files
authored
gg: mipmap generation when using sokol (#27206)
1 parent edc1d4e commit ef5d8d6

2 files changed

Lines changed: 105 additions & 62 deletions

File tree

vlib/gg/image.c.v

Lines changed: 104 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub mut:
1616
width int
1717
height int
1818
nr_channels int
19+
nr_mipmaps int
1920
ok bool
2021
data voidptr
2122
ext string
@@ -26,6 +27,13 @@ pub mut:
2627
texture_filter TextureFilter = .linear
2728
}
2829

30+
@[params]
31+
pub struct ImageConfig {
32+
pub:
33+
texture_filter ?TextureFilter
34+
max_mipmaps int = 1 // a value below 1 means as many as possible
35+
}
36+
2937
// destroy GPU resources associated with the image
3038
fn (image &Image) destroy() {
3139
if image.ok {
@@ -38,17 +46,18 @@ fn (image &Image) destroy() {
3846
}
3947
}
4048

41-
// create_image creates an `Image` from `file`.
42-
pub fn (mut ctx Context) create_image(file string) !Image {
43-
return ctx.create_image_with_filter(file, ctx.config.texture_filter)
44-
}
45-
4649
// create_image_with_filter creates an `Image` from `file` with the requested texture filter.
50+
@[deprecated: 'use Context.create_image instead']
4751
pub fn (mut ctx Context) create_image_with_filter(file string, texture_filter TextureFilter) !Image {
52+
return ctx.create_image(file, texture_filter: texture_filter)!
53+
}
54+
55+
// create_image creates an `Image` from `file`.
56+
pub fn (mut ctx Context) create_image(file string, cfg ImageConfig) !Image {
4857
if !os.exists(file) {
4958
$if android {
5059
image_data := os.read_apk_asset(file)!
51-
mut image := ctx.create_image_from_byte_array_with_filter(image_data, texture_filter)!
60+
mut image := ctx.create_image_from_byte_array(image_data, cfg)!
5261

5362
image.path = file
5463

@@ -57,15 +66,11 @@ pub fn (mut ctx Context) create_image_with_filter(file string, texture_filter Te
5766
return error('image file "${file}" not found')
5867
}
5968
}
60-
6169
$if macos {
6270
if ctx.native_rendering {
63-
// return C.darwin_create_image(file)
6471
mut img := C.darwin_create_image(file)
65-
img.texture_filter = texture_filter
72+
img.texture_filter = cfg.texture_filter or { ctx.config.texture_filter }
6673

67-
// println('created macos image: ${img.path} w=${img.width}')
68-
// C.printf('p = %p\n', img.data)
6974
img.id = ctx.image_cache.len
7075
unsafe {
7176
ctx.image_cache << img
@@ -74,33 +79,83 @@ pub fn (mut ctx Context) create_image_with_filter(file string, texture_filter Te
7479
}
7580
}
7681

77-
if !gfx.is_valid() {
78-
// Sokol is not initialized yet, add stbi object to a queue/cache
79-
// ctx.image_queue << file
80-
mut img := load_image_with_filter(file, texture_filter)!
81-
img.ok = false
82-
img.id = ctx.image_cache.len
83-
unsafe {
84-
ctx.image_cache << img
85-
}
86-
return img
87-
}
88-
mut img := create_image(file, texture_filter)!
82+
mut img := load_image(file)!
8983
img.id = ctx.image_cache.len
84+
img.texture_filter = cfg.texture_filter or { ctx.config.texture_filter }
9085
unsafe {
9186
ctx.image_cache << img
9287
}
88+
if gfx.is_valid() {
89+
img.init_sokol_image()
90+
}
9391
return img
9492
}
9593

94+
// adapted from https://github.com/floooh/sokol/issues/102#issuecomment-2926566603
95+
fn sokol_mipmap(mut simg_desc gfx.ImageDesc, max_mipmaps int) {
96+
mut levels := 0
97+
mut size := 0
98+
width := simg_desc.width
99+
height := simg_desc.height
100+
mipmaps := if max_mipmaps < gfx.sg_max_mipmaps && max_mipmaps > 0 {
101+
max_mipmaps
102+
} else {
103+
gfx.sg_max_mipmaps
104+
}
105+
for i in 1 .. mipmaps {
106+
w := width >> i
107+
h := height >> i
108+
if w < 1 || h < 1 { break
109+
}
110+
size += w * h * 4 // 4 = img.nr_channels
111+
levels++
112+
}
113+
// use unsafe to access the data because we have to anyway
114+
mut target := unsafe { malloc(size) }
115+
mut src_width := width
116+
mut src_height := height
117+
for level in 1 .. levels {
118+
src := &u8(simg_desc.data.subimage[0][level - 1].ptr)
119+
target_width := src_width / 2
120+
target_height := src_height / 2
121+
for x in 0 .. target_width {
122+
for y in 0 .. target_height {
123+
// channels RGBA
124+
chans := 4
125+
for ch in 0 .. 4 {
126+
// avererage colors in a 2x2 square over the source image where (x*2|y*2) is the lower left corner
127+
mut color := 0
128+
sx := x * 2
129+
sy := y * 2
130+
color += unsafe { src[(sx + src_width * sy) * chans + ch] }
131+
color += unsafe { src[(sx + src_width * (sy + 1)) * chans + ch] }
132+
color += unsafe { src[((sx + 1) + src_width * (sy + 1)) * chans + ch] }
133+
color += unsafe { src[((sx + 1) + src_width * sy) * chans + ch] }
134+
color /= 4
135+
unsafe {
136+
target[(x + y * target_width) * chans + ch] = u8(color)
137+
}
138+
}
139+
}
140+
}
141+
src_width = target_width
142+
src_height = target_height
143+
simg_desc.data.subimage[0][level].ptr = target
144+
simg_desc.data.subimage[0][level].size = usize(target_width * target_height * 4)
145+
unsafe {
146+
target += target_width * target_height * 4
147+
}
148+
}
149+
simg_desc.num_mipmaps = levels
150+
}
151+
96152
// init_sokol_image initializes this `Image` for use with the
97153
// sokol graphical backend system.
98154
pub fn (mut img Image) init_sokol_image() &Image {
99155
// println('\n init sokol image ${img.path} ok=${img.simg_ok}')
100156
mut img_desc := gfx.ImageDesc{
101-
width: img.width
102-
height: img.height
103-
num_mipmaps: 0
157+
width: img.width
158+
height: img.height
104159
// wrap_u: .clamp_to_edge // XTODO SAMPLER
105160
// wrap_v: .clamp_to_edge
106161
label: &char(img.path.str)
@@ -123,14 +178,17 @@ pub fn (mut img Image) init_sokol_image() &Image {
123178
ptr: img.data
124179
size: img_size
125180
}
181+
sokol_mipmap(mut img_desc, img.nr_mipmaps)
182+
img.nr_mipmaps = img_desc.num_mipmaps
126183
img.simg = gfx.make_image(&img_desc)
127184
gfx_filter := img.texture_filter.gfx_filter()
128185

129186
mut smp_desc := gfx.SamplerDesc{
130-
min_filter: gfx_filter
131-
mag_filter: gfx_filter
132-
wrap_u: .clamp_to_edge
133-
wrap_v: .clamp_to_edge
187+
min_filter: gfx_filter
188+
mag_filter: gfx_filter
189+
mipmap_filter: .linear
190+
wrap_u: .clamp_to_edge
191+
wrap_v: .clamp_to_edge
134192
}
135193

136194
img.ssmp = gfx.make_sampler(&smp_desc)
@@ -216,33 +274,16 @@ pub fn (mut ctx Context) create_image_with_size(file string, width int, height i
216274
if !gfx.is_valid() {
217275
// Sokol is not initialized yet, add stbi object to a queue/cache
218276
// ctx.image_queue << file
219-
mut img := load_image_with_filter(file, ctx.config.texture_filter) or { return Image{} }
277+
mut img := load_image(file) or { return Image{} }
278+
img.texture_filter = ctx.config.texture_filter
220279
img.width = width
221280
img.height = height
222281
img.ok = false
223282
img.id = ctx.image_cache.len
224283
ctx.image_cache << img
225284
return img
226285
}
227-
mut img := create_image(file, ctx.config.texture_filter) or { return Image{} }
228-
img.id = ctx.image_cache.len
229-
ctx.image_cache << img
230-
return img
231-
}
232-
233-
// create_image creates an `Image` from `file`.
234-
//
235-
// TODO: remove this
236-
fn create_image(file string, texture_filter TextureFilter) !Image {
237-
mut img := load_image_with_filter(file, texture_filter)!
238-
img.init_sokol_image()
239-
return img
240-
}
241-
242-
fn load_image_with_filter(file string, texture_filter TextureFilter) !Image {
243-
mut img := load_image(file)!
244-
img.texture_filter = texture_filter
245-
return img
286+
return ctx.create_image(file) or { Image{} }
246287
}
247288

248289
fn load_image(file string) !Image {
@@ -265,12 +306,7 @@ fn load_image(file string) !Image {
265306
// memory buffer `buf` of size `bufsize`.
266307
//
267308
// See also: create_image_from_byte_array
268-
pub fn (mut ctx Context) create_image_from_memory(buf &u8, bufsize int) !Image {
269-
return ctx.create_image_from_memory_with_filter(buf, bufsize, ctx.config.texture_filter)
270-
}
271-
272-
// create_image_from_memory_with_filter creates an `Image` from `buf` with the requested texture filter.
273-
pub fn (mut ctx Context) create_image_from_memory_with_filter(buf &u8, bufsize int, texture_filter TextureFilter) !Image {
309+
pub fn (mut ctx Context) create_image_from_memory(buf &u8, bufsize int, cfg ImageConfig) !Image {
274310
stb_img := stbi.load_from_memory(buf, bufsize)!
275311
mut img := Image{
276312
width: stb_img.width
@@ -280,26 +316,33 @@ pub fn (mut ctx Context) create_image_from_memory_with_filter(buf &u8, bufsize i
280316
data: stb_img.data
281317
ext: stb_img.ext
282318
id: ctx.image_cache.len
283-
texture_filter: texture_filter
319+
texture_filter: cfg.texture_filter or { ctx.config.texture_filter }
284320
}
285-
if gfx.is_valid() && !ctx.native_rendering {
321+
if gfx.is_valid() {
286322
img.init_sokol_image()
287323
}
288324
ctx.image_cache << img
289325
return img
290326
}
291327

328+
// create_image_from_memory_with_filter creates an `Image` from `buf` with the requested texture filter.
329+
@[deprecated: 'use Context.create_image_from_memory instead']
330+
pub fn (mut ctx Context) create_image_from_memory_with_filter(buf &u8, bufsize int, texture_filter TextureFilter) !Image {
331+
return ctx.create_image_from_memory(buf, bufsize, texture_filter: texture_filter)
332+
}
333+
292334
// create_image_from_byte_array creates an `Image` from the
293335
// byte array `b`.
294336
//
295337
// See also: create_image_from_memory
296-
pub fn (mut ctx Context) create_image_from_byte_array(b []u8) !Image {
297-
return ctx.create_image_from_byte_array_with_filter(b, ctx.config.texture_filter)
338+
pub fn (mut ctx Context) create_image_from_byte_array(b []u8, cfg ImageConfig) !Image {
339+
return ctx.create_image_from_memory(b.data, b.len, cfg)
298340
}
299341

300342
// create_image_from_byte_array_with_filter creates an `Image` from `b` with the requested texture filter.
343+
@[deprecated: 'use Context.create_image_from_byte_array instead']
301344
pub fn (mut ctx Context) create_image_from_byte_array_with_filter(b []u8, texture_filter TextureFilter) !Image {
302-
return ctx.create_image_from_memory_with_filter(b.data, b.len, texture_filter)
345+
return ctx.create_image_from_memory(b.data, b.len, texture_filter: texture_filter)
303346
}
304347

305348
pub struct StreamingImageConfig {

vlib/gg/image_test.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ fn test_create_image_from_byte_array_uses_context_texture_filter() {
7979
fn test_create_image_from_byte_array_with_filter_overrides_context_default() {
8080
mut ctx := gg.new_context(width: 100)
8181
background_bytes := os.read_bytes(background_path)!
82-
img := ctx.create_image_from_byte_array_with_filter(background_bytes, .nearest)!
82+
img := ctx.create_image_from_byte_array(background_bytes, texture_filter: .nearest)!
8383
assert img.texture_filter == .nearest
8484
assert ctx.get_cached_image_by_idx(img.id).texture_filter == .nearest
8585
}

0 commit comments

Comments
 (0)