floooh / sokol-nim

nim bindings for https://github.com/floooh/sokol
MIT License
76 stars 14 forks source link

SIGBUS: Illegal storage access. (Attempt to read from nil?) when using`makeImage` #32

Closed Nazariglez closed 2 days ago

Nazariglez commented 2 days ago

I have an issue trying to render images. I am not sure what's going on because it happen using images with more than 2809 pixels... a size of 53x53 works, but no higher.

One way to test this is loading an image as:

proc getImage(): Image =
  var data = newSeq[uint8](0)
  var width, height, channels: int

  const f = staticRead("./assets/img.png")
  data = stbi.loadFromMemory(cast[seq[uint8]](f), width, height, channels, stbi.RGBA)

  let desc = sg.ImageDesc(
    width: cast[int32](width),
    height: cast[int32](height),
    data: ImageData(
      subimage: [ [ sg.Range(addr: addr data, size: width * height * 4) ] ]
    ),
  )
  sg.makeImage(desc)

Another is altering the texcube example to increase the size of the generated texture:

#-------------------------------------------------------------------------------
#   texcube.nim
#   Texture creation, rendering with texture, packed vertex components.
#-------------------------------------------------------------------------------
import sokol/log as slog
import sokol/gfx as sg
import sokol/app as sapp
import sokol/glue as sglue
import math/vec3
import math/mat4
import shaders/texcube as shd

proc generateCheckerboard(width: int, height: int): seq[uint32] =
  var pixels: seq[uint32] = @[]
  for y in 0..<height:
    for x in 0..<width:
      if (x div 8 + y div 8) mod 2 == 0:
        pixels.add(0xFFFFFFFF'u32)  # white
      else:
        pixels.add(0xFF000000'u32)  # black
  return pixels

var
  rx, ry: float32
  pip: Pipeline
  bindings: Bindings

const
  passAction = PassAction(
    colors: [
      ColorAttachmentAction( loadAction: loadActionClear, clearValue: (0.25, 0.5, 0.75, 1.0))
    ]
  )

type Vertex = object
  x, y, z: float32
  color: uint32
  u, v: uint16

proc init() {.cdecl.} =
  sg.setup(sg.Desc(
    environment: sglue.environment(),
    logger: sg.Logger(fn: slog.fn),
  ))

  #[
    Cube vertex buffer with packed vertex formats for color and texture coords.
    Note that a vertex format which must be portable across all
    backends must only use the normalized integer formats
    (BYTE4N, UBYTE4N, SHORT2N, SHORT4N), which can be converted
    to floating point formats in the vertex shader inputs.
  ]#
  const vertices = [
    Vertex( x: -1.0, y: -1.0, z: -1.0,  color: 0xFF0000FF'u32, u:     0, v:     0 ),
    Vertex( x:  1.0, y: -1.0, z: -1.0,  color: 0xFF0000FF'u32, u: 32767, v:     0 ),
    Vertex( x:  1.0, y:  1.0, z: -1.0,  color: 0xFF0000FF'u32, u: 32767, v: 32767 ),
    Vertex( x: -1.0, y:  1.0, z: -1.0,  color: 0xFF0000FF'u32, u:     0, v: 32767 ),
    Vertex( x: -1.0, y: -1.0, z:  1.0,  color: 0xFF00FF00'u32, u:     0, v:     0 ),
    Vertex( x:  1.0, y: -1.0, z:  1.0,  color: 0xFF00FF00'u32, u: 32767, v:     0 ),
    Vertex( x:  1.0, y:  1.0, z:  1.0,  color: 0xFF00FF00'u32, u: 32767, v: 32767 ),
    Vertex( x: -1.0, y:  1.0, z:  1.0,  color: 0xFF00FF00'u32, u:     0, v: 32767 ),
    Vertex( x: -1.0, y: -1.0, z: -1.0,  color: 0xFFFF0000'u32, u:     0, v:     0 ),
    Vertex( x: -1.0, y:  1.0, z: -1.0,  color: 0xFFFF0000'u32, u: 32767, v:     0 ),
    Vertex( x: -1.0, y:  1.0, z:  1.0,  color: 0xFFFF0000'u32, u: 32767, v: 32767 ),
    Vertex( x: -1.0, y: -1.0, z:  1.0,  color: 0xFFFF0000'u32, u:     0, v: 32767 ),
    Vertex( x:  1.0, y: -1.0, z: -1.0,  color: 0xFFFF007F'u32, u:     0, v:     0 ),
    Vertex( x:  1.0, y:  1.0, z: -1.0,  color: 0xFFFF007F'u32, u: 32767, v:     0 ),
    Vertex( x:  1.0, y:  1.0, z:  1.0,  color: 0xFFFF007F'u32, u: 32767, v: 32767 ),
    Vertex( x:  1.0, y: -1.0, z:  1.0,  color: 0xFFFF007F'u32, u:     0, v: 32767 ),
    Vertex( x: -1.0, y: -1.0, z: -1.0,  color: 0xFFFF7F00'u32, u:     0, v:     0 ),
    Vertex( x: -1.0, y: -1.0, z:  1.0,  color: 0xFFFF7F00'u32, u: 32767, v:     0 ),
    Vertex( x:  1.0, y: -1.0, z:  1.0,  color: 0xFFFF7F00'u32, u: 32767, v: 32767 ),
    Vertex( x:  1.0, y: -1.0, z: -1.0,  color: 0xFFFF7F00'u32, u:     0, v: 32767 ),
    Vertex( x: -1.0, y:  1.0, z: -1.0,  color: 0xFF007FFF'u32, u:     0, v:     0 ),
    Vertex( x: -1.0, y:  1.0, z:  1.0,  color: 0xFF007FFF'u32, u: 32767, v:     0 ),
    Vertex( x:  1.0, y:  1.0, z:  1.0,  color: 0xFF007FFF'u32, u: 32767, v: 32767 ),
    Vertex( x:  1.0, y:  1.0, z: -1.0,  color: 0xFF007FFF'u32, u:     0, v: 32767 ),
  ]
  bindings.vertexBuffers[0] = sg.makeBuffer(BufferDesc(
    data: sg.Range(addr: vertices.addr, size: vertices.sizeof)
  ))

  # create an index buffer for the cube
  const indices = [
    0'u16, 1, 2,  0, 2, 3,
    6, 5, 4,  7, 6, 4,
    8, 9, 10,  8, 10, 11,
    14, 13, 12,  15, 14, 12,
    16, 17, 18,  16, 18, 19,
    22, 21, 20,  23, 22, 20
  ]
  bindings.indexBuffer = sg.makeBuffer(BufferDesc(
    type: bufferTypeIndexBuffer,
    data: sg.Range(addr: indices.addr, size: indices.sizeof)
  ))

  let h:int32 = 54
  let w:int32 = 53

  # create a checker board texture
  let pixels = generateCheckerboard(w, h)
  bindings.fs.images[shd.slotTex] = sg.makeImage(sg.ImageDesc(
    width: w,
    height: h,
    data: ImageData(
      subimage: [ [ sg.Range(addr: pixels.addr, size: w*h*4) ] ]
    )
  ))

  # create a matching sampler
  bindings.fs.samplers[shd.slotSmp] = sg.makeSampler(sg.SamplerDesc(
    minFilter: filterNearest,
    magFilter: filterNearest,
  ));

  # shader and pipeline object
  pip = sg.makePipeline(PipelineDesc(
    shader: sg.makeShader(texcubeShaderDesc(sg.queryBackend())),
    layout: VertexLayoutState(
      attrs: [
        VertexAttrState(format: vertexFormatFloat3),    # pos
        VertexAttrState(format: vertexFormatUbyte4n),   # color0
        VertexAttrState(format: vertexFormatShort2n)    # texcoord0
      ]
    ),
    indexType: indexTypeUint16,
    cullMode: cullModeBack,
    depth: DepthState(
      compare: compareFuncLessEqual,
      writeEnabled: true
    )
  ))

proc computeVsParams(): shd.VsParams =
  let proj = persp(60.0f, sapp.widthf()/sapp.heightf(), 0.01f, 10.0f)
  let view = lookat(vec3(0.0f, 1.5f, 6.0f), vec3.zero(), vec3.up())
  let rxm = rotate(rx, vec3(1f, 0f, 0f))
  let rym = rotate(ry, vec3(0f, 1f, 0f))
  let model = rxm * rym
  result = VsParams(mvp: proj * view * model)

proc frame() {.cdecl.} =
  let dt = sapp.frameDuration() * 60f
  rx += 1f * dt
  ry += 2f * dt
  let vsParams = computeVsParams()
  sg.beginPass(Pass(action: passAction, swapchain: sglue.swapchain()))
  sg.applyPipeline(pip)
  sg.applyBindings(bindings)
  sg.applyUniforms(shaderStageVs, shd.slotVsParams, sg.Range(addr: vsParams.addr, size: vsParams.sizeof))
  sg.draw(0, 36, 1)
  sg.endPass()
  sg.commit()

proc cleanup() {.cdecl.} =
  sg.shutdown()

sapp.run(sapp.Desc(
  initCb: init,
  frameCb: frame,
  cleanupCb: cleanup,
  windowTitle: "texcube.nim",
  width: 800,
  height: 600,
  sampleCount: 4,
  icon: IconDesc(sokol_default: true),
  logger: sapp.Logger(fn: slog.fn),
))

this will panic at runtime with:

➜  sokol-nim git:(master) ✗ nimble texcube
  Verifying dependencies for sokol@0.5.1
  Executing task texcube in /Users/nazariglez/personal/sokol-nim/sokol.nimble
Hint: used config file '/opt/homebrew/Cellar/nim/2.0.6/nim/config/nim.cfg' [Conf]
Hint: used config file '/opt/homebrew/Cellar/nim/2.0.6/nim/config/config.nims' [Conf]
..................................................................................
CC: ../../../../../opt/homebrew/Cellar/nim/2.0.6/nim/lib/system.nim
CC: ../../../.nimble/pkgs2/sokol-0.5.5-afa97955f1370ea45bf6b8e512b309004fc9eb6a/sokol/gfx.nim
CC: texcube.nim
Hint:  [Link]
ld: warning: ignoring duplicate libraries: '-lm'
Hint: mm: orc; threads: on; opt: none (DEBUG BUILD, `-d:release` generates faster code)
35722 lines; 0.254s; 46.449MiB peakmem; proj: examples/texcube; out: /Users/nazariglez/personal/sokol-nim/build/texcube [SuccessX]
Traceback (most recent call last)
/Users/nazariglez/personal/sokol-nim/examples/texcube.nim(158) texcube
/Users/nazariglez/.nimble/pkgs2/sokol-0.5.5-afa97955f1370ea45bf6b8e512b309004fc9eb6a/sokol/app.nim(560) run
/Users/nazariglez/personal/sokol-nim/examples/texcube.nim(102) init
/Users/nazariglez/.nimble/pkgs2/sokol-0.5.5-afa97955f1370ea45bf6b8e512b309004fc9eb6a/sokol/gfx.nim(1381) makeImage
SIGBUS: Illegal storage access. (Attempt to read from nil?)
stack trace: (most recent call last)
/private/var/folders/zw/1nf4s1_92b1fkgcbgqr604t80000gn/T/nimblecache-1799278598/nimscriptapi_1188391125.nim(211, 16)
/Users/nazariglez/personal/sokol-nim/sokol.nimble(91, 3) texcubeTask
/Users/nazariglez/personal/sokol-nim/sokol.nimble(69, 3) run
/opt/homebrew/Cellar/nim/2.0.6/nim/lib/system/nimscript.nim(265, 7) exec
/opt/homebrew/Cellar/nim/2.0.6/nim/lib/system/nimscript.nim(265, 7) Error: unhandled exception: FAILED: build/texcube [OSError]
       Tip: 1 messages have been suppressed, use --verbose to show them.
nimscriptwrapper.nim(161) execScript

    Error:  Exception raised during nimble script execution

I think that I am missing something, or there is a bug somewhere in nim or the sokol bindings. Does this rings any bell?

Thanks!

floooh commented 2 days ago

I don't know Nim enough to analyze the problem tbh. The error looks like it's trying to access invalid memory, or the nullptr. The main difference to the original samples seems to be the use of Nim sequences. Where do those store their data and how is lifetime of that data handled? If Nim's garbage collection or RAII-like memory management is involved, maybe the data is already freed before the makeImage() function is entered?

Nazariglez commented 2 days ago

I thought the same. I am new to nim too. Sequences are stored in the heap as far as I know. I added some operations after the make image to keep it alive the pixels and the desc (stored it in a independent variable), but the same result.

Then I tried changing the memory management options to refc and none. Both of them work with an image of 54x54 and crashes with more pixels, while orc(the default) only work with images up to 53x53.

Last attempt, I generated an array[155*154, uint32] manually and paste it into the code to be used as image data, and it works no matter the memory option I pass to the compiler.

Screenshot 2024-07-04 at 10 59 49

My guess is that I should share this issue in the nim's forum to see if any fellow there knows what could be wrong and how to fix it.

Edit: Tested with nim 2.0.6 and 2.0.8

Edit2: Thread in nim forum: https://forum.nim-lang.org/t/11925

griffith1deady commented 2 days ago

@Nazariglez you give pointer to sequence object, not sequence memory. this line:

subimage: [ [ sg.Range(addr: addr data, size: width * height * 4) ] ]

should be:

subimage: [ [ sg.Range(addr: addr data[0], size: width * height * 4) ] ]
Nazariglez commented 2 days ago

Oh wow, this works, thank you!

Would you happen to have any resources or information about why this is the case? I am checking nim-by-example and other resources, and it seems that data[0] should access the first element in the sequence, similar to array behavior. So I am a bit lost in understanding why this is happening.

Also, this means that there is no way to pass an array and a sequence interchangeably because we need to know the type to pass data or data[0]. This also makes me wonder why it works with small images but not with "big" images.

I know I have a lot of questions. I am closing the issue because it is not related to sokol itself. However, if you have the time and willingness, it would be really helpful to me to know more about this. In any case, thank you so much.